在 Node/Express 的异步函数中捕获错误

Posted

技术标签:

【中文标题】在 Node/Express 的异步函数中捕获错误【英文标题】:Catching an error in an async function in Node/Express 【发布时间】:2019-11-26 16:29:04 【问题描述】:

在从中间件或路由处理程序调用 Express next()res.send() 后,有什么方法可以捕获异步回调中发生的错误?考虑以下代码:

app.use('/throw-error', (req, res) => 
  setTimeout(() => 
    throw new Error('Async error causes thread death')
  , 500)

  res.send('This thread is going to die...')
)

它将执行并向浏览器发送“This thread will die ...”。半秒后,它运行的 Node 线程也会崩溃。如果你碰巧正在运行一个使用 Node 的 cluster 模块的应用程序,它可能会启动一个新线程,但它还是死了。您可能会在日志中看到类似的内容:

::1 [2019-07-17T18:54:55.142Z] - [71700] 4.740 ms "GET /throw-error" 200 -
/Users/moryl/Projects/crashtest/express.js:66
      throw new Error('Async error causes thread death')
      ^

Error: Async error causes thread death
    at Timeout.setTimeout [as _onTimeout] (/Users/moryl/Projects/InSight/sources/server/config/express.js:66:13)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)

那个线程现在已经死了。

我的问题是,您到底是如何处理超出正常请求范围的(可能未知)异步错误,无论是设计使然还是坏事代码?如何防止线程死亡?

我不想被告知我不应该在一开始就在异步调用中做这种事情。我知道这个。我正在尝试编写防御性代码来捕捉其他人编写的“坏东西”。

【问题讨论】:

我认为您需要更具体地说明 “一个奇怪的异步错误”,您是在谈论随机/间歇性且难以追踪的错误吗?还是您在谈论您知道的预期错误?后者相当简单…… @James 我说的是未知错误。我不是想调试一些错误。我可以很容易地做到这一点。我试图让这些错误在被发现时不会对线程造成致命影响。如果这样可以减少混淆,我会从问题中删除“奇怪”这个词。 “未知错误”的来源并不多。大多数库(例如 socket.io)提供错误事件,你应该听一下。 @MichaelOryl 如果我们谈论的是 expected 错误,例如数据库连接失败或文件丢失等,那么您自然会想要处理这些,因为我们知道这些可能会发生。如果我们谈论的是 unexpected 错误,那么实际上,让应用崩溃往往比不安全更安全,因为这是我们没有预料到的,因此无法保证应用的状态。 @James 不,也不是在谈论预期的错误。 API 发生变化。这个应用程序中的很多代码都是由外部公司编写的——有时很糟糕。当输入系统的数据发生变化时,就会出现新问题。我正在尝试检测此类问题并更优雅地处理它们。此答案末尾的链接 (***.com/a/57082917/1480995) 将允许我这样做。 【参考方案1】:

这已记录在expresserror handling doc:

必须捕获路由调用的异步代码中发生的错误 处理程序 或中间件并将它们传递给 Express 进行处理。为了 示例:

app.get('/', function (req, res, next) 
  setTimeout(function () 
    try 
      throw new Error('BROKEN')
     catch (err) 
      next(err)
    
  , 100)
)

上面的示例使用 try...catch 块来捕获错误 异步代码并将它们传递给 Express。 如果 try...catch 块 被省略,Express 不会捕获错误,因为它不是一部分 同步处理程序代码。

所以,基本上你需要try..catch 路由。 (例子基本相同,巧合之母)

【讨论】:

除非您引入自己的错误处理程序不会尝试发送错误响应,否则调用 next 会无意中导致不同的错误,因为响应已经已发送。 我不是在问如何正确捕获代码中的错误。 setTimeout() 只是一个示例,它会导致模拟问题的异步错误。这个答案没有解决问题。不过,谢谢。【参考方案2】:

我的问题是,你到底是如何处理超出正常请求范围的异步错误的,无论是设计...

您仍然希望处理异步代码中的错误,即使它被设计触发和遗忘。为每个独立任务添加try catch .catch。使用异步代码,Promises 和 async / await帮助你(因为它们将独立的回调分组到任务中,然后你可以处理每个任务的错误):

const timer = ms => new Promise(res => setTimeout(res, ms));

async function fireAndForgetThis() 
  await timer(500);
  throw new Error('Async error doesn't cause thread death, because its handled properly')


 fireAndForgetThis()
   .catch(console.error); // But always "handle" errors

...还是通过错误的代码?

修复错误代码。

如何防止线程死机?

这不是你想要阻止的事情。如果发生错误并且未得到处理,您的应用程序将进入计划外状态。继续执行可能会产生更多问题。你不想要那个。您想防止未处理的拒绝/未处理的错误本身(通过正确处理)。


当然有些情况是您无法处理的,例如如果与后备数据库的连接断开。在这种情况下,NodeJS 崩溃,导致监控唤醒 DevOps,使数据库重新运行。崩溃也是一种处理错误的形式;)


如果您读到这里,但仍想处理未处理的错误,请不要这样做。好吧,你可能有你的理由,there you go。

【讨论】:

您答案中的最后一个链接实际上很有用。剩下的就是我已经知道(并且没有问过)和 snark 的事情的组合。我知道如何在我们编写的代码中处理这种事情,以及如何修复由于日志中显示的错误而暴露的错误代码。问题是如何更通用地处理这种情况,因为我们的 API 会改变并产生新的问题,而且我们从外部公司继承了数千行代码。您指向事件处理程序的最后一个链接至少可以让我们清楚地记录此类事件。谢谢。 不,这不是恶作剧,至少它不打算作恶作剧。这是一个必要的警告。在 99% 的情况下,您希望正确处理错误。

以上是关于在 Node/Express 的异步函数中捕获错误的主要内容,如果未能解决你的问题,请参考以下文章

如何在嵌套异步函数中捕获错误

异步 - 等待 JavaScript:无法从错误对象中捕获错误详细信息 [重复]

捕获回调函数引发的错误

从全局反应应用程序中的异步函数中捕获错误

如何在 node.js 中捕获同步函数中的错误?

在颤动的异步函数中捕获 PlatformException