异步node.js调用中的错误处理

error handling in asynchronous node.js calls

本文关键字:错误 处理 调用 node js 异步      更新时间:2023-09-26

我是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。对于具有许多不同和复杂处理程序的复杂服务器,这是不可接受的。

我可以使用一个全局异常处理程序(防止服务器完全崩溃),但是我不能向用户发送响应,因为我不知道哪个请求导致了异常。这也意味着请求保持未处理/打开状态,浏览器将永远等待响应。

有人有好的,坚如磐石的解决方案吗?

Node 0.8引入了一个名为"域"的新概念。它们非常类似于。net中的appdomain,并提供了一种封装一组IO操作的方法。它们基本上允许您将请求处理调用包装在特定于上下文的组中。如果该组抛出任何未捕获的异常,则可以以一种方式处理和处理它们,该方式使您能够访问所有特定于范围和上下文的信息,以便成功地从错误中恢复(如果可能的话)。

这个特性是刚刚引入的新特性,所以要谨慎使用,但据我所知,它是专门为解决OP试图解决的问题而引入的。

文档可以在:http://nodejs.org/api/domain.html

在node.js中检出uncaughtException处理程序。它捕获被抛出到事件循环中的错误。

http://nodejs.org/docs/v0.4.7/api/process.html event_uncaughtException_

但是不抛出错误总是更好的解决方案。你可以只做一个return res.end('Unabled to load file xxx');

这是Node现在的问题之一。实际上不可能跟踪哪个请求导致了回调中抛出的错误。

如果可能的话,您将不得不在回调本身中处理错误(其中您仍然有对请求和响应对象的引用)。uncaughtException处理程序将阻止节点进程退出,但是从用户的角度来看,首先引起异常的请求将挂起。

问得好。我现在正面临同样的问题。也许最好的方法是使用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);
});

我还没有测试这个解决方案,但我不明白为什么这不能工作。

我给自己的问题一个答案…:)

似乎没有办法手动捕获错误。我现在使用一个辅助函数,它本身返回一个包含try/catch块的函数。此外,我自己的web服务器类检查请求处理函数是否调用response.end() try/catch helper函数waitfor()(否则引发异常)。这在很大程度上避免了开发人员错误地不保护请求。这不是一个100%容易出错的解决方案,但对我来说已经足够好了。

handler.waitfor = function(callback) {
  var me=this;
  // avoid exception because response.end() won't be called immediately:
  this.waiting=true;
  return function() {
    me.waiting=false;
    try {
      callback.apply(this, arguments);
      if (!me.waiting && !me.finished)
        throw new Error("Response handler returned and did neither send a "+
          "response nor did it call waitfor()");
    } catch (e) {
      me.handleException(e);
    }
  }
}

为了安全起见,我只需要添加一个内联的waitfor()调用。

function handleRequest(request, response, handler) {
  fs.read(fd, buffer, 0, 10, null, handler.waitfor(
    function(error, bytesRead, buffer) {
      buffer.unknownFunction();  // causes exception
      response.end(buffer);
    }
  )); //fs.read
}

实际的检查机制有点复杂,但应该清楚它是如何工作的。如果有人感兴趣,我可以在这里发布完整的代码。

一个想法是:您可以使用helper方法来创建回调,并将其作为您的标准实践。这确实给开发人员带来了负担,但至少你可以有一个"标准"的方式来处理你的回调,这样忘记一个回调的机会就很低了:

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(或一般的函数式编程)的优点之一是您可以轻松地为这样的事情使用自己的工具。

在撰写本文时,我看到的方法是使用"Promises"。

http://howtonode.org/promises
https://www.promisejs.org/

允许代码和回调被很好地结构化以进行错误管理,也使其更具可读性。它主要使用.then()函数。

someFunction().then(success_callback_func, failed_callback_func);

下面是一个基本的例子:

  var SomeModule = require('someModule');
  var success = function (ret) {
      console.log('>>>>>>>> Success!');
  }
  var failed = function (err) {
    if (err instanceof SomeModule.errorName) {
      // Note: I've often seen the error definitions in SomeModule.errors.ErrorName
      console.log("FOUND SPECIFIC ERROR");
    }
    console.log('>>>>>>>> FAILED!');
  }
  someFunction().then(success, failed);
  console.log("This line with appear instantly, since the last function was asynchronous.");

两件事确实帮助我在代码中解决了这个问题。

  1. 'longjohn'模块,它允许您查看完整的堆栈跟踪(跨多个异步回调)。
  2. 一种简单的闭包技术,用于将异常保持在标准callback(err, data)习语中(在CoffeeScript中显示)。

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

现在您可以包装不安全的代码,并且您的回调都以相同的方式处理错误:通过检查error参数。

我最近创建了一个名为WaitFor的简单抽象,用于在同步模式下调用异步函数(基于纤维):https://github.com/luciotato/waitfor

它太新了,不可能是"坚如磐石"。

使用<<p> strong>等。对于,您可以使用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以同步模式调用node的async函数,没有(可见的)回调,所以我可以把所有的代码放在一个try-catch块中。

等。for将抛出异常,如果任何异步函数返回err!= = null

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

同样在同步多线程编程(例如。net, Java, PHP)中,当捕获自定义未知异常时,您不能返回任何有意义的信息给客户端。当你没有关于异常的信息时,你可以返回HTTP 500。

因此,"秘密"在于填充一个描述性的错误对象,这样你的错误处理程序可以从有意义的错误映射到正确的HTTP状态+可选的描述性结果。但是,你还必须在异常到达process.on('uncaughtException')之前捕获异常: Step1:定义一个有意义的错误对象
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;

步骤2:当抛出一个异常时,用属性填充它(参见步骤1),允许处理程序将其转换为有意义的HTTP结果:

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

Step3:当调用一些潜在的危险代码时,捕获错误并重新抛出该错误,同时在error对象

中填充额外的上下文属性

Step4:您必须在请求处理期间捕获异常。如果你使用一些领先的承诺库(BlueBird很好),它可以让你捕获异步错误,这就容易多了。如果你不能使用承诺,那么任何内置的NODE库都会在回调时返回错误。

Step5:现在您的错误被捕获并且包含了关于发生的事情的描述性信息,您只需要将其映射到有意义的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...
}

您可以在这里找到其他相关的最佳实践