即使您已经接受了另一个答案,我也会在这里添加一个答案,因为我认为这里发生的事情可以更好地解释,这将有助于其他人试图理解这一点。
在您的代码中:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
让我们讨论一下发生了什么:
async
首先,您声明了为了在其中使用而必须执行的回调await
。一个async
函数告诉解释器做几件重要的事情。
1. 异步函数总是返回一个 Promise。 承诺的解析值将是函数返回的任何值。
2. async 函数在内部用try/catch
. 如果在函数代码的顶级范围内抛出任何异常,那么这些异常将被捕获并自动拒绝函数返回的承诺。
3. 异步功能允许您使用await
. 这是对解释器的一个指示,它应该实现并允许await
函数内部的语法。这与上面的前两点有关,这就是为什么您不能await
在任何 'ol 函数中使用。任何未捕获的拒绝await
也将拒绝函数返回的承诺。
重要的是要理解,虽然async/await
语法允许您编写带有异常的程序并像同步代码一样尝试/捕获,但它并不完全相同。该函数仍会立即返回一个承诺,并且函数中未捕获的异常会导致该承诺在稍后的某个时间被拒绝。它们不会导致同步异常冒泡到调用者。因此,Expresstry/catch
不会看到同步异常。
但是在这里,错误不会被 try/catch 捕获
我认为 Express 用 try/catch 包装了所有中间件函数,所以我看不出它的行为会有什么不同?
那么为什么那里的 try/catch [in Express] 没有捕获抛出的错误?
这有两个原因:
被拒绝的承诺不是同步抛出,因此 Express 无法通过 try/catch 捕获它。该函数只返回一个被拒绝的承诺。
Express 根本没有查看路由处理程序回调的返回值(您可以在您显示的 Express 代码中看到)。因此,您的async
函数返回一个后来被拒绝的承诺这一事实完全被 Express 忽略了。它只是这样做fn(req, res, next);
并且不注意返回的承诺。因此,拒绝承诺被置若罔闻。
有一个类似于 Express 的框架叫做Koa,它大量使用了 Promise,并且确实注意返回的 Promise,它会看到你拒绝的 Promise。但是,这不是 Express 所做的。
如果你想在 Express 中实现一些 Koa 类型的功能,你可以自己实现它。为了让其他功能不受干扰,使其能够正常工作,我将实现一个名为的新方法getAsync
,该方法确实使用了 Promise:
router.getAsync = function(...args) {
let fn = args.pop();
// replace route with our own route wrapper
args.push(function(req, res, next) {
let p = fn(req, res, next);
// if it looks like a promise was returned here
if (p && typeof p.catch === "function") {
p.catch(err => {
next(err);
});
}
});
return router.get(...args);
}
然后你可以这样做:
router.getAsync('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
而且,它会正确调用next(err)
您的错误。
或者,您的代码甚至可以是这样的:
router.getAsync('/force_async_error/1', function (req, res, next) {
return Promise.reject(new Error('my zoom 1'));
});
PS 在完整的实现中,您可能会制作一堆动词的异步版本,然后为中间件实现它,然后将其放在路由器原型上。但是,这个例子是向你展示它是如何工作的,而不是在这里做一个完整的实现。