8

我无法理解如何处理似乎是 express 的一个非常基本的方面。如果我有一些代码在异步回调中引发异常,则我无法捕获该异常,因为在回调运行时 try/catch 块已不在范围内。在这些情况下,浏览器将挂起,直到它最终放弃声明服务器无响应。这是非常糟糕的用户体验。我宁愿能够立即向客户端返回 500 错误。默认的快速错误处理程序显然不能处理这种情况。这是一些示例代码:

var express = require("express");

var app = express();
app.use(app.router);
//express error handler (never called)
app.use(function(err, req, res, next) {
    console.log(err);
    res.send(500);
});

app.get("/test", function(req, res, next) {
    require("fs").readFile("/some/file", function(err, data) {
        a.b(); //blow up
    });
});

app.listen(8888);

在上面的代码中,ab() 行抛出了“ReferenceError: a is not defined”异常。定义的错误处理程序永远不会被调用。请注意,在这种情况下 fs.readFile() 返回的 err 对象为空,因为文件已被正确读取。错误是异步处理程序中的代码。

我什至已经阅读了这篇关于使用节点的 uncaughtExpception 的帖子,但文档说不要使用该方法。即使我确实使用了它,我将如何将 500 响应发送回用户?快速响应对象不再供我使用。

那么你如何处理这种情况呢?

4

2 回答 2

7

好的,我只是要发布一个完全不同的答案,因为我的第一个答案有一些有价值的信息,但事后看来是离题了。

简短的回答:正确的做法是已经发生的事情:您的程序应该打印堆栈跟踪并退出并出现错误。

基本思路

所以我认为你需要考虑不同类别的错误。我的第一个答案涉及与数据相关的错误,一个编写良好的程序可以而且应该干净地处理这些错误。你所描述的是一个崩溃。如果您阅读了您链接到的 node.js 文档,那么它是正确的。此时您的程序可以做的唯一有用的事情是退出堆栈跟踪并允许进程主管重新启动它并达到可理解的状态。一旦你的程序崩溃了,它基本上是不可恢复的,因为错误的范围非常广泛,这可能是异常到达堆栈顶部的根本原因。在您的特定示例中,此错误每次都会继续发生,直到源代码错误得到修复并重新部署应用程序。如果您担心未经测试和错误的代码会进入您的应用程序,

但简而言之,不,没有办法获得对导致此异常的 HTTP 请求对象的引用,因此 AFAIK 您无法更改最终用户在浏览器中感知的方式,除了在中间反向代理层处理此问题您可以在其中配置粗略的超时并发送更友好的错误页面(这对于任何不是完整 HTML 文档的请求当然是无用的)。

Node 中的错误处理圣经

在我看来,Dave Pacheco在 Node.js 中的错误处理是该主题的权威著作。它是全面的、广泛的和彻底的。我建议定期阅读和重新阅读。


为了解决@asparagino 的评论,如果未处理的异常很容易重现或发生频率很高,那不是边缘情况,而是错误。正确的做法是改进您的代码以在这种情况下不生成未捕获的异常。实际处理条件,从而将程序员错误转换为操作错误,您的程序可以继续运行而无需重新启动并且不会出现未捕获的异常。

于 2013-08-26T05:08:19.680 回答
5

您应该使用 express 的错误处理中间件 via app.use(error, req, res, next)。Express 维护一个单独的中间件堆栈,当普通的中间件堆栈抛出未捕获的异常时,它会使用该堆栈。(旁注,express 只是查看回调的 arity(预期参数的数量)将其归类为常规中间件或错误处理中间件,这有点神奇,所以请记住,您必须像上面一样声明参数以便 express 理解这是一个错误处理中间件)。

根据您的问题和评论,只需了解异常在 node.js 中并不是那么有用,因为每个异步调用都会获得一个新堆栈,这就是为什么到处都使用回调并且第一个参数是普遍错误的原因。您try/catch在示例中的块只会捕获直接抛出的异常findById(例如如果id未定义,就像在您的代码段中一样),但是一旦对数据库进行了实际调用,就是这样,调用堆栈结束并且没有在节点调用异步 IO 回调时启动完全不同的调用堆栈之前,可能会发生更多异常。

感谢您的回答,但这仅在我将 try/catch 放在异步回调中并让 catch 执行 next(exp) 时才有效。我想避免在每个异步回调中都有单独的 try/catch 块。

不,那不是真的。您不必手动调用next(exp). Express 将捕获错误并为您触发错误处理中间件(这就是 express 在开发模式下开发人员友好的异常报告页面的方式)。即使在“正常”错误条件下,异步库也不会抛出异常。他们将错误传递给回调,因此通常您不必在 node.js 中尝试/捕获那么多。只要永远不要忽略传递给回调函数的错误参数,就可以了。

您在节点中看不到此样板:

someDb.query(someCriteria, function (error, result) {
  try {
    //some code to deal with result
  } catch (exception) {
    callback(exception);
  }
});

你确实看到了这一点:

someDb.query(someCriteria, function (error, result) {
  if (error) {
    callback(error);
    return;
  }
  //some code to deal with result
});

Node 处理 IO 的方式不同,这意味着调用堆栈的工作方式不同,这意味着异常的工作方式不同,这意味着错误处理的工作方式不同。您可以编写一个稳定的 node/express 应用程序来处理错误而不会崩溃,而无需编写单个 try/catch。(express 有一个可以处理未捕获的错误,这些错误一直冒泡到顶部)。这不是功能限制,它只是异步 IO 的结果,这意味着您必须以不同的方式编写代码并使用回调而不是异常来处理错误。将其视为“限制”而不是“现状”是对实际上只是技术现实的事物的负面含义。在同步和异步范式中都有干净而健壮的异常处理模式。

于 2013-08-23T18:02:16.787 回答