有时候异步代码中的错误为什么会导致 Node.js 服务器崩溃?

4
我在一些网站上读到,在express.js中,“任何异步代码中未捕获的错误可能会导致DoS崩溃HTTP服务器”。我做了这个例子来检查它,但我想知道为什么如果错误发生在express回调内部,服务器不会崩溃,但如果它发生在setTimeout()函数内部,则服务器会崩溃。
这两个示例中不都是异步代码中发生错误吗?还是其中一个不是异步的,我弄错了吗?为什么某些异步代码中的未捕获错误会使服务器崩溃,而其他异步代码中不会呢?
var express = require("express");

var app = express();

http: app.get("/e1", (req, res, next) => {
  let p = req.query.p;
  let pn = parseInt(p, 10);

  //If the error happens here the server does not crashes
  let s = pn + y; // y does not exist, so an error occurs

  res.send("hi");
});

http: app.get("/e2", (req, res, next) => {
  let p = req.query.p;
  let pn = parseInt(p, 10);

  setTimeout(() => {
    //If the error happens here the server crashes
    let s = pn + y; // y does not exist, so an error occurs
  }, 100);

  res.send("hi");
});

app.listen(3000, function() {
  console.log("Example app listening on port 3000!");
});

验证输入并使用try/catch。 - charlietfl
Express默认处理错误吗? - v8rs
1个回答

1

如果我们将throwcatch视为在堆栈上操作,可能会更加清晰:

throw:沿着堆栈向下查找处理程序,然后从那里继续执行。

catch将错误处理程序添加到堆栈中。

对于同步代码,可以将其可视化为:

 // Legend:
 -> function call
 <- function returns

 http.request -> express.handler -> [try] -> your function -> nested call -> Throw!
 <-            <-                   [catch] <-----------------------------------

现在,当您启动异步操作时,回调函数将在某个时间点被调用,并且该回调函数最终会出现在一个新的堆栈中:
 // the current stack:
 http.request -> express.handler -> [try] -> your function -> start async action
 <-            <-                 <-       <-             <-

 // somewhen later, the timeout calls back
 timer -> your setTimeout callback -> nested call -> Throw!
 Crash! <-----------------------------------------------------

现在,由于Express将catch处理程序附加到回调中,因此您的代码如下所示:
 Express.get = function(callback) {
   //...
   try {
     callback(req, res, next);
   } catch(error) {
    // handle error
   }
 };

这将处理错误,但仅限同步错误(它是简化的,实际代码在这里)。


现在应该怎么做来处理这个问题呢?
基本上:将每个回调函数包装成一个 Promise(这样可以更容易地处理异步错误):
 const delay = ms => new Promise(res => setTimeout(res, ms));

然后创建每个Promise并使用await等待,将所有内容包装在try/catch中:

 app.get(async (req, res) => {
   try {
      await delay(2000);
      const p = q + d;
   } catch(error) {
     res.status(500).send("whoops");
  }
});

这是因为await“保持堆栈”(但仅限于嵌套调用的async函数的堆栈,这就是为什么我们需要添加自己的try/catch,因为Express不会在回调调用上await),因此,在一个嵌套的await函数中发生的错误将退回到我们的错误处理程序。
 http.request -> express.handler -> [async] -> [try] -> nested call -> [await]
 <-                     <-               <-
 // synchronous code returned, the [async] stack will be kept
 [async] -> [try] -> nested call -> [await]

// somewhen, the promise resolves, execution coninues:
[async] -> [try] -> nested call -> Throw!
<-       <- [catch]  <--------------

在回调函数调用周围加上try { } catch(e) { }块的@v8rs - Jonas Wilms
@v8rs 因为 Express 已经为您添加了一个。 - Jonas Wilms
哦,我明白了,那很有道理。你应该在你的回答中更清楚地表达,以便将来有相同疑问的 Stack Overflow 用户能够理解。 - v8rs
@v8rs 如果Express将一个catch处理程序附加到回调函数并进入您的代码,则不确定如何使其更清晰... - Jonas Wilms
由于“if”这个词,我理解为“Express可以附加一个catch处理程序,但默认情况下不会附加”。 - v8rs
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接