为什么有时异步代码中的错误会导致 node.js 服务器崩溃
Why sometimes error in async code makes the node.js server crash
我在 express.js 'Any uncaught errors in async code could crash the HTTP server causing DoS' 的一些网站上看到过。
我做了这个例子来检查它,但我想知道为什么如果错误发生在快速回调中,服务器不会崩溃,但如果它发生在 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!");
});
如果我们想到 throw
和 catch
在 堆栈上操作,可能会变得清楚 :
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
}
};
这将处理错误,但只是同步错误(经过简化,实际代码是 here)
现在应该怎么做才能解决这个问题?
基本上:将每个回调包装成一个承诺(因为这使得异步错误处理更容易):
const delay = ms => new Promise(res => setTimeout(res, ms));
然后 await
您创建的每一个承诺,并将所有内容包装在 try
/ catch
:
app.get(async (req, res) => {
try {
await delay(2000);
const p = q + d;
} catch(error) {
res.status(500).send("whoops");
}
});
之所以可行,是因为 await
"keeps the stack"(但只是 await
在嵌套调用中的 async
函数之一,这就是为什么我们需要添加自己的 try
/ catch
因为 Express 不会 await
在回调调用中),因此嵌套 await
ed 函数之一内部的错误将回退到我们的错误处理程序。
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] <--------------
我在 express.js 'Any uncaught errors in async code could crash the HTTP server causing DoS' 的一些网站上看到过。 我做了这个例子来检查它,但我想知道为什么如果错误发生在快速回调中,服务器不会崩溃,但如果它发生在 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!");
});
如果我们想到 throw
和 catch
在 堆栈上操作,可能会变得清楚 :
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
}
};
这将处理错误,但只是同步错误(经过简化,实际代码是 here)
现在应该怎么做才能解决这个问题?
基本上:将每个回调包装成一个承诺(因为这使得异步错误处理更容易):
const delay = ms => new Promise(res => setTimeout(res, ms));
然后 await
您创建的每一个承诺,并将所有内容包装在 try
/ catch
:
app.get(async (req, res) => {
try {
await delay(2000);
const p = q + d;
} catch(error) {
res.status(500).send("whoops");
}
});
之所以可行,是因为 await
"keeps the stack"(但只是 await
在嵌套调用中的 async
函数之一,这就是为什么我们需要添加自己的 try
/ catch
因为 Express 不会 await
在回调调用中),因此嵌套 await
ed 函数之一内部的错误将回退到我们的错误处理程序。
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] <--------------