【问题标题】:Handling exceptions in express在 express 中处理异常
【发布时间】:2015-01-31 06:30:16
【问题描述】:

我无法理解如何处理似乎是 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);

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

我已经阅读了this post 关于使用节点的 uncaughtExpception 的内容,但 the documentation 说不要使用该方法。即使我确实使用了它,我将如何将 500 响应发送回用户?快速响应对象不再供我使用。

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

【问题讨论】:

  • 为什么会在回调中抛出错误?更有可能的是,MongooseObject 会在内部抛出一个错误,您可以在其中捕获它,然后 callback(err)。现在上面显示的回调有一个虚假的错误对象,您可以发送 500 响应。
  • 如果 Mongoose 遇到它不回调错误的情况......嗯我不知道也许你可以制作一个 setTimeout 包装器。但我希望所有错误都会立即传递给回调
  • 我担心的不是 Mongoose,我只是用它来调用异步函数。我更担心我自己的代码会做一些愚蠢的事情并导致抛出异常。我将更新示例代码以使其更加清晰。
  • 为什么不把 try/catch 块放在回调里面,把你关心的那部分代码包装起来呢?您是否正在寻找一个全球性的包罗万象以避免在任何地方这样做?
  • 是的,我不想在我拥有的每个异步代码块中都放置尝试捕获,这将是数百个。不过,将它放在每个顶级快递处理程序中可能没问题。

标签: node.js exception express


【解决方案1】:

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

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

基本思路

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

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

Node 中的错误处理圣经

Error Handling in Node.js by Dave Pacheco 在我看来是关于这个主题的权威著作。它是全面的、广泛的和彻底的。我建议定期阅读和重新阅读。


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

【讨论】:

  • 感谢彼得的回答。这是我从我一直在阅读的内容中所怀疑的。我不确定我是否 100% 同意这样的想法,即程序在遇到意外异常时唯一有用的事情就是重新启动。显然,从最终用户的角度来看,这不是一个好的体验。除此之外,它还会影响当时正在进行的所有其他请求。我编写了许多 Web 应用程序,它们利用顶级异常处理程序返回 500,记录错误,但保持服务正常运行。无论如何,它的工作方式是它的工作方式,我只需要处理它。再次感谢。
  • 是的,我认为这主要是基于事件的 IO 的不可避免的结果。 JavaScript 的超级动态特性也使错误更容易进入生产环境。也许其他答案会出现一些好的建议。
  • @PeterLyons - 一个节点进程通常会处理数十甚至数千个并发请求。其中每一个都可以包含有价值的数据,如果不完整,将会产生成本。你正在建造一个杂耍者。它应该一丢掉一个球就丢掉所有的球吗?应记录错误和异常。应该审查日志,并跟踪和修复错误和边缘条件。该进程不应退出。
  • 优雅地处理现有请求有时是可能的,有时是不可能的。可以关闭您的 http 服务器,以便新请求停止到达并有一个短暂的宽限期,以便当前请求可以在可能的情况下完成,然后退出并重新启动。关键是继续无限期地运作会使事情变得更糟。如果未捕获的异常影响到每个请求,那么无论如何您都无法干净地处理它。
  • @PeterLyons - 我认为可能存在很多变量,并且对于很多应用程序(包括我正在研究的一些应用程序)来说,一个简单的“异常退出”教条是错误的。在我们的场景中,在遇到边缘条件后重新启动高流量应用程序后,一旦我们再次开始接收数据,我们可能会在几分之一秒内再次体验到它。不断重启流程只会让我们更多而不是更少地被破坏,并将部分故障变成灾难性的。我想我们可能同意“这取决于你在做什么”。
【解决方案2】:

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

根据您的问题和 cmets,只需了解异常在 node.js 中并不是那么有用,因为每个异步调用都会获得一个新堆栈,这就是为什么到处都使用回调并且第一个参数是普遍错误的原因。示例中的 try/catch 块只会捕获由 findById 直接抛出的异常(例如,如果 id 未定义,就像在您的 sn-p 中一样),但是一旦对数据库进行了实际调用,就是这样,调用堆栈已经结束,并且在节点调用异步 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 的结果,这意味着您必须以不同的方式编写代码并使用回调而不是异常来处理错误。将其视为“限制”而不是“现状”是对实际上只是技术现实的事物的负面含义。在同步和异步范式中都有清晰而健壮的异常处理模式。

【讨论】:

  • 感谢您的回答,但这仅在我将 try/catch 放在异步回调中并让 catch 执行 next(exp) 时才有效。我想避免在每个异步回调中都有单独的 try/catch 块。
  • 是的,我想我理解这个问题,我只是不知道该怎么办。在某些时候,代码会抛出异常,我不希望节点进程崩溃或挂起。这只是节点的固有限制吗?
  • 彼得,感谢您抽出宝贵时间发布详尽的答案。要么我没有完全正确地解释我的问题,要么我在错误处理代码中做错了什么。您说“Express 将捕获错误并为您触发错误处理中间件”,但我似乎无法完成这项工作。我修改了我的问题以包含一个简单而完整的示例来演示我所看到的。
  • 准备提供有用的信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-26
  • 2012-10-25
相关资源
最近更新 更多