【问题标题】:error handling in asynchronous node.js calls异步 node.js 调用中的错误处理
【发布时间】:2011-08-14 13:23:51
【问题描述】:

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

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

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

【问题讨论】:

    标签: javascript exception-handling callback node.js


    【解决方案1】:

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

    此功能是新功能,刚刚引入,因此请谨慎使用,但据我所知,它是专门为解决 OP 试图解决的问题而引入的。

    可以在以下位置找到文档:http://nodejs.org/api/domain.html

    【讨论】:

    【解决方案2】:

    检查 node.js 中的 uncaughtException 处理程序。它捕获引发的错误,这些错误冒泡到事件循环中。

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

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

    【讨论】:

    • 感谢您的回答,但正如描述中所写,uncaughtException 无济于事,因为我无法在其中结束请求,因为我没有 request 或 @987654326 @ 参考。而且你不能完全避免异常(毕竟我喜欢它们并且经常使用它们)。
    【解决方案3】:

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

    如果可能的话,您将不得不在回调本身中处理您的错误(您仍然可以引用请求和响应对象)。 uncaughtException 处理程序将阻止节点进程退出,但从用户的角度来看,最初导致异常的请求只会挂在那里。

    【讨论】:

      【解决方案4】:

      非常好的问题。我现在正在处理同样的问题。可能最好的方法是使用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);
      });
      

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

      【讨论】:

      • 我正在成功使用我现在在自己的答案中描述的解决方案,并且效果很好。自定义 HttpException 仅适用于自行生成的异常,但不适用于其他地方生成的异常,例如插件或 3rd 方脚本,它们不是 HttpException。在保留上下文信息的同时捕获 any 异常的唯一方法是使用 try .. catch
      • ...当然,异常也可能是由拼写错误引起的,例如拼写错误的函数名。
      • 根据节点文档,您需要非常小心地使用此解决方案。请参阅我的答案以更好地处理这种情况。 “不要使用它,而是使用域。如果您使用它,请在每次未处理的异常后重新启动您的应用程序!”来源:nodejs.org/api/process.html
      • 这不是疯了吗?
      【解决方案5】:

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

      似乎没有办法手动捕获错误。我现在使用一个辅助函数,它本身返回一个包含 try/catch 块的 function。此外,我自己的 Web 服务器类检查请求处理函数是否调用 response.end() try/catch 帮助函数 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
      }
      

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

      【讨论】:

      • 当否决我的解决方案时,请添加评论并描述原因。我已经在许多项目中使用这种方法。它就像一个魅力,我还找不到更好的选择。
      • 不是我对你投了反对票,但对此我确实有一个问号。链式回调呢?换句话说,你有一个回调,它调用另一个接受回调的函数,依此类推。除非我弄错了,否则这里看起来您必须将 every 回调包装在 waitfor 函数中。是这样吗?
      • 基本上,是的。 waitfor() 函数只是确保在回调中抛出的“任何”异常都可以在正确的上下文中被捕获和处理,而不是作为一个通用的未处理异常结束。如前所述,不幸的是,解决方案并不完美,但我还没有看到更好的解决方案。
      • +1 反对在没有任何解释的情况下给出的反对票。这个解决方案看起来确实有点不优雅,但如果一个不优雅的解决方案是最好的,那么我们只能忍受它。
      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        在撰写本文时,我看到的方法是使用“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.");
        

        【讨论】:

        • 承诺在这里没有帮助。如果一个 Promise 执行一个引发异常的异步调用,NodeJS 进程也会停止。尝试 setTimeout(function(){ throw new Exception()}) 在你的 someFunction 中看看会发生什么。
        【解决方案8】:

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

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

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

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

        【讨论】:

          【解决方案9】:

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

          “坚如磐石”太新了。

          使用 wait.for 您可以像同步一样使用异步函数,而不会阻塞节点的事件循环。几乎和你习惯的一样:

          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

          【讨论】:

            【解决方案10】:

            此外,在同步多线程编程(例如 .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;
            

            第二步:当抛出异常时,用属性填充它(参见步骤 1),允许处理程序将其转换为平均 HTTP 结果:

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

            第 3 步:在调用一些潜在危险代码时,捕获错误并重新抛出该错误,同时在 Error 对象中填充额外的上下文属性

            Step4:您必须在请求处理期间捕获异常。如果您使用一些领先的 ​​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...
            }
            

            你还可以related best practices here

            【讨论】:

              猜你喜欢
              • 2019-03-21
              • 2016-03-30
              • 2016-04-29
              • 2016-12-13
              • 2020-05-24
              • 1970-01-01
              • 2015-05-22
              • 2020-11-22
              • 2013-01-25
              相关资源
              最近更新 更多