javascript - promise教學 - 異步node.js調用中的錯誤處理




javascript promise教學 (7)

一個想法:您可以使用助手方法來創建回調,並將其作為您的標準做法來使用它。 這確實給開發者帶來了沉重的負擔,但是至少你可以有一個“標準”的方式來處理你的回調,以至於忘記一個回調的機會很低:

var callWithHttpCatch = function(response, fn) {
    try {
        fn && fn();
    }
    catch {
        response.writeHead(500, {'Content-Type': 'text/plain'}); //No
    }
}

<snipped>
      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          callWithHttpCatch(response, buffer.dontTryThisAtHome());  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

我知道這可能不是你正在尋找的答案,但是關於ECMAScript(或者一般的函數式編程)的好處之一就是你可以很容易地為自己的工具推出類似的東西。

我是新來的node.js,雖然我一般熟悉JavaScript。 我的問題是關於如何處理node.js中的錯誤的“最佳實踐”。

通常,在編寫Web服務器,FastCGI服務器或各種語言的網頁時,我在多線程環境中使用阻塞處理程序的異常。 當一個請求進來,我通常做這樣的事情:

function handleRequest(request, response) {
  try {

    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

這樣,我總是可以處理內部錯誤,並發送有效的響應給用戶。

我明白,與node.js應該做非阻塞調用,這顯然會導致不同數量的回調,如在這個例子中:

var sys    = require('sys'),
    fs     = require('fs');

require("http").createServer(handleRequest).listen(8124);

function handleRequest(request, response) {

  fs.open("/proc/cpuinfo", "r",
    function(error, fd) {
      if (error)
        throw new Error("fs.open error: "+error.message);

      console.log("File open.");

      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          buffer.dontTryThisAtHome();  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

}

這個例子將完全終止服務器,因為異常沒有被捕獲。 我的問題是在這裡,我不能再使用一個單一的try / catch,因此通常不會在處理請求時發現任何可能出現的錯誤。

當然,我可以在每個回調中添加一個try / catch,但是我不喜歡這種方法,因為那麼程序員就不會忘記 try / catch。 對於具有許多不同且複雜的處理程序的複雜服務器,這是不可接受的。

我可以使用全局異常處理程序(防止完整的服務器崩潰),但是我不能發送響應給用戶,因為我不知道哪個請求導致異常。 這也意味著請求仍然未處理/打開,瀏覽器正在等待響應。

有人有一個好的,堅如磐石的解決方案?


同樣,在同步多線程編程(例如.NET,Java,PHP)中,當捕獲到自定義的未知異常時,您不能將任何有意義的信息返回給客戶端。 如果您沒有關於例外的信息,您可能會返回HTTP 500。

因此,“秘密”在於填充一個描述性的錯誤對象,這樣你的錯誤處理程序可以從有意義的錯誤映射到正確的HTTP狀態+可選的描述性結果。 但是,在到達process.on('uncaughtException')之前,您還必須捕獲異常:

第一步:定義一個有意義的錯誤對象

function appError(errorCode, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.errorCode = errorCode;
    //...other properties assigned here
};

appError.prototype.__proto__ = Error.prototype;
module.exports.appError = appError;

第二步:拋出一個異常時,填充屬性(見第一步),允許處理程序將其轉換為meannigul HTTP結果:

throw new appError(errorManagement.commonErrors.resourceNotFound, "further explanation", true)

第3步:調用一些潛在的危險代碼時,捕獲錯誤並重新拋出該錯誤,同時在Error對像中填充其他上下文屬性

第四步:您必須在請求處理過程中捕獲異常。 如果你使用一些領先的Promise庫(BlueBird很棒),這可以讓你捕捉異步錯誤。 如果你不能使用promise,那麼任何內建的NODE庫都會在回調中返回錯誤。

第5步:現在您的錯誤被捕獲並包含有關發生的描述性信息,您只需將其映射到有意義的HTTP響應。 這裡最好的部分是你可能有一個集中的,單一的錯誤處理程序來獲取所有的錯誤,並將它們映射到HTTP響應:

    //this specific example is using Express framework
    res.status(getErrorHTTPCode(error))
function getErrorHTTPCode(error)
{
    if(error.errorCode == commonErrors.InvalidInput)
        return 400;
    else if...
}

您可以在這裡找到其他相關的最佳做


很好的問題。 我現在正在處理同樣的問題。 可能最好的方法是使用uncaughtException 。 請求對象和請求對象的引用不是問題,因為可以將它們包裝到異常對像中,即傳遞給uncaughtException事件。 像這樣的東西:

var HttpException = function (request, response, message, code) {

  this.request = request;
  this.response = response;  
  this.message = message;    
  this.code = code || 500;

}

丟它:

throw new HttpException(request, response, 'File not found', 404);

並處理響應:

process.on('uncaughtException', function (exception) {
  exception.response.writeHead(exception.code, {'Content-Type': 'text/html'});
  exception.response.end('Error ' + exception.code + ' - ' + exception.message);
});

我還沒有測試這個解決方案,但我不明白為什麼這不能工作。


我最近創建了一個名為WaitFor的簡單抽象,以同步模式調用異步函數(基於Fibers): https//github.com/luciotato/waitfor

“堅如磐石”太新了。

使用wait.for可以使用async函數,就好像它們是同步的,而不會阻塞節點的事件循環。 這幾乎是你曾經習慣的:

var wait=require('wait.for');

function handleRequest(request, response) {
      //launch fiber, keep node spinning
      wait.launchFiber(handleinFiber,request, response); 
}

function handleInFiber(request, response) {
  try {
    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response, callback) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

由於您處於光纖中,因此可以按順序編程“阻塞光纖”,而不是節點的事件循環。

另一個例子:

var sys    = require('sys'),
    fs     = require('fs'),
    wait   = require('wait.for');

require("http").createServer( function(req,res){
      wait.launchFiber(handleRequest,req,res) //handle in a fiber
  ).listen(8124);

function handleRequest(request, response) {
  try {
    var fd=wait.for(fs.open,"/proc/cpuinfo", "r");
    console.log("File open.");
    var buffer = new require('buffer').Buffer(10);

    var bytesRead=wait.for(fs.read,fd, buffer, 0, 10, null);

    buffer.dontTryThisAtHome();  // causes exception

    response.end(buffer);
  }
  catch(err) {
    response.end('ERROR: '+err.message);
  }

}

正如你所看到的,我使用wait.for在同步模式下調用節點的異步函數,沒有(可見的)回調,所以我可以在一個try-catch塊中包含所有的代碼。

如果任何一個異步函數返回err!== null, wait.for將會拋出一個異常

更多信息在https://github.com/luciotato/waitfor


有兩件事真的幫助我解決了我的代碼中的這個問題。

  1. 'longjohn'模塊,可以讓你看到完整的堆棧跟踪(跨越多個異步回調)。
  2. 一個簡單的閉包技術,用於在標準callback(err, data)習慣用法(在這裡顯示在CoffeeScript中)中保留異常。

    ferry_errors = (callback, f) ->
      return (a...) ->
        try f(a...)
        catch err
          callback(err)

現在,您可以包裝不安全的代碼,並且您的回調函數都以相同的方式處理錯誤:通過檢查錯誤參數。



這是Node現在的問題之一。 追踪哪個請求導致回調中出現錯誤幾乎是不可能的。

如果可能的話,你將不得不在回調中自己處理你的錯誤(如果你仍然有對請求和響應對象的引用)。 uncaughtException處理程序將阻止節點進程退出,但引起異常的請求首先從用戶的角度掛起。





node.js