Express 中间件无法捕获 async/await 抛出的错误,但为什么呢?
Express middleware cannot trap errors thrown by async/await, but why?
这两个中间件函数的行为不同,我不明白为什么:
这里,错误会被困在try/catch:
router.get('/force_async_error/0', async function (req, res, next) {
try{
await Promise.reject(new Error('my zoom 0'));
}
catch(err){
next(err);
}
});
但在这里,错误将 而不是 被 try/catch:
困住
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
我以为 Express 用 try/catch 包装了所有中间件函数,所以我看不出它会有什么不同?
我查看了 Express 源代码,处理程序如下所示:
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
};
那么为什么 try/catch 没有捕获抛出的错误?
这是因为调用是异步的,拿这段代码来说:
try {
console.log('Before setTimeout')
setTimeout(() => {
throw new Error('Oups')
})
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err)
}
console.log("Point of non-return, I can't handle anything anymore")
如果你 运行 它你应该看到错误是在 Point of non-return
之后触发的。
当我们在 throw
行时为时已晚,我们在 try
/catch
之外。此时如果抛出错误,将不会被捕获。
您可以通过在调用方中使用async
/await
来解决这个问题(对被调用方来说无关紧要),即:
void async function () {
try {
console.log('Before setTimeout')
await new Promise((resolve, reject) =>
setTimeout(() => {
reject(new Error('Oups'))
})
)
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err.stack)
}
console.log("Point of non-return, I can't handle anything anymore")
}()
最后,这意味着要让 Express 处理异步错误,您需要将代码更改为:
async function handle(req, res, next) {
// [...]
try {
await fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
}
更好的解决方法:
像这样定义一个 wrap
函数:
const wrap = fn => (...args) => Promise
.resolve(fn(...args))
.catch(args[2])
并像这样使用它:
app.get('/', wrap(async () => {
await Promise.reject('It crashes!')
}))
即使您已经接受了另一个答案,我也会在这里添加一个答案,因为我认为这里发生的事情可以得到更好的解释,这将有助于其他人试图理解这一点。
在您的代码中:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
让我们讨论一下发生了什么:
首先,您将回调声明为 async
,您必须这样做才能在其中使用 await
。 async
函数告诉解释器做几件重要的事情。
1.异步函数始终 return 是一个承诺。 承诺的解析值将是函数 return 的任何值。
2。异步函数在内部用 try/catch
. 包装,如果在函数代码的顶级范围内抛出任何异常,那么这些异常将被捕获并自动拒绝函数 returns.
3。异步函数允许您使用 await
. 这向解释器指示它应该实现并允许函数内部的 await
语法。这与上面的前两点有关,这就是为什么不能在任何 'ol 函数中使用 await
的原因。来自 await
的任何未捕获的拒绝也将拒绝函数 returns.
的承诺
重要的是要理解,虽然 async/await
语法允许您编写带有异常的程序,而 try/catch 类似于同步代码,但它们并不完全相同。该函数仍在 return 立即承诺,函数中未捕获的异常导致该承诺在稍后的某个时间被拒绝。它们不会导致同步异常冒泡到调用者。因此,Express try/catch
不会看到同步异常。
But here, the error will not get trapped by try/catch
I thought Express wrapped all middleware functions with try/catch, so I don't see how it would behave differently?
so why doesn't the try/catch [in Express] there capture the thrown error?
这有两个原因:
被拒绝的承诺不是同步抛出,因此 Express 无法用 try/catch 捕获它。该函数只是 return 一个被拒绝的承诺。
Express 根本不查看路由处理程序回调的 return 值(您可以在显示的 Express 代码中看到这一点)。因此,您的 async
函数 return 是一个后来被拒绝的承诺这一事实被 Express 完全忽略了。它只是这样做 fn(req, res, next);
而不会注意 returned 承诺。因此,对诺言的拒绝被置若罔闻。
有一个名为 Koa 的有点 Express-like 的框架,它经常使用 promises 并且确实关注 returned promises 并且会看到你拒绝的 promise。但是,这不是 Express 所做的。
如果您想要 Express 中的一些 Koa-type 功能,您可以自己实现。为了让其他功能不受干扰,以便它可以正常工作,我将实现一个名为 getAsync
的新方法,该方法确实使用了承诺:
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'));
});
P.S。在一个完整的实现中,你可能会制作一堆动词的异步版本,你会为中间件实现它,然后把它放在路由器原型上。但是,这个例子是为了向您展示它是如何工作的,而不是在这里做一个完整的实现。
这些都没有真正回答问题,如果我理解正确的话是:
由于 async/await 语法允许您使用非异步样式 try/catch 语法处理被拒绝的 "awaits",为什么不失败"await" 由 Express' try/catch 在顶级处理并为您变成 500?
我相信答案是 Express 内部调用你的任何函数也必须用 "async" 声明并用 "await" 调用你的处理程序以启用异步捕获 try/catch 在该级别工作。
想知道 Express 团队是否有功能请求?他们只需要在两个地方添加两个关键字。如果成功,什么也不做,如果异常移交给错误处理堆栈。
这两个中间件函数的行为不同,我不明白为什么:
这里,错误会被困在try/catch:
router.get('/force_async_error/0', async function (req, res, next) {
try{
await Promise.reject(new Error('my zoom 0'));
}
catch(err){
next(err);
}
});
但在这里,错误将 而不是 被 try/catch:
困住router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
我以为 Express 用 try/catch 包装了所有中间件函数,所以我看不出它会有什么不同?
我查看了 Express 源代码,处理程序如下所示:
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
};
那么为什么 try/catch 没有捕获抛出的错误?
这是因为调用是异步的,拿这段代码来说:
try {
console.log('Before setTimeout')
setTimeout(() => {
throw new Error('Oups')
})
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err)
}
console.log("Point of non-return, I can't handle anything anymore")
如果你 运行 它你应该看到错误是在 Point of non-return
之后触发的。
当我们在 throw
行时为时已晚,我们在 try
/catch
之外。此时如果抛出错误,将不会被捕获。
您可以通过在调用方中使用async
/await
来解决这个问题(对被调用方来说无关紧要),即:
void async function () {
try {
console.log('Before setTimeout')
await new Promise((resolve, reject) =>
setTimeout(() => {
reject(new Error('Oups'))
})
)
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err.stack)
}
console.log("Point of non-return, I can't handle anything anymore")
}()
最后,这意味着要让 Express 处理异步错误,您需要将代码更改为:
async function handle(req, res, next) {
// [...]
try {
await fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
}
更好的解决方法:
像这样定义一个 wrap
函数:
const wrap = fn => (...args) => Promise
.resolve(fn(...args))
.catch(args[2])
并像这样使用它:
app.get('/', wrap(async () => {
await Promise.reject('It crashes!')
}))
即使您已经接受了另一个答案,我也会在这里添加一个答案,因为我认为这里发生的事情可以得到更好的解释,这将有助于其他人试图理解这一点。
在您的代码中:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
让我们讨论一下发生了什么:
首先,您将回调声明为 async
,您必须这样做才能在其中使用 await
。 async
函数告诉解释器做几件重要的事情。
1.异步函数始终 return 是一个承诺。 承诺的解析值将是函数 return 的任何值。
2。异步函数在内部用 try/catch
. 包装,如果在函数代码的顶级范围内抛出任何异常,那么这些异常将被捕获并自动拒绝函数 returns.
3。异步函数允许您使用 await
. 这向解释器指示它应该实现并允许函数内部的 await
语法。这与上面的前两点有关,这就是为什么不能在任何 'ol 函数中使用 await
的原因。来自 await
的任何未捕获的拒绝也将拒绝函数 returns.
重要的是要理解,虽然 async/await
语法允许您编写带有异常的程序,而 try/catch 类似于同步代码,但它们并不完全相同。该函数仍在 return 立即承诺,函数中未捕获的异常导致该承诺在稍后的某个时间被拒绝。它们不会导致同步异常冒泡到调用者。因此,Express try/catch
不会看到同步异常。
But here, the error will not get trapped by try/catch
I thought Express wrapped all middleware functions with try/catch, so I don't see how it would behave differently?
so why doesn't the try/catch [in Express] there capture the thrown error?
这有两个原因:
被拒绝的承诺不是同步抛出,因此 Express 无法用 try/catch 捕获它。该函数只是 return 一个被拒绝的承诺。
Express 根本不查看路由处理程序回调的 return 值(您可以在显示的 Express 代码中看到这一点)。因此,您的
async
函数 return 是一个后来被拒绝的承诺这一事实被 Express 完全忽略了。它只是这样做fn(req, res, next);
而不会注意 returned 承诺。因此,对诺言的拒绝被置若罔闻。
有一个名为 Koa 的有点 Express-like 的框架,它经常使用 promises 并且确实关注 returned promises 并且会看到你拒绝的 promise。但是,这不是 Express 所做的。
如果您想要 Express 中的一些 Koa-type 功能,您可以自己实现。为了让其他功能不受干扰,以便它可以正常工作,我将实现一个名为 getAsync
的新方法,该方法确实使用了承诺:
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'));
});
P.S。在一个完整的实现中,你可能会制作一堆动词的异步版本,你会为中间件实现它,然后把它放在路由器原型上。但是,这个例子是为了向您展示它是如何工作的,而不是在这里做一个完整的实现。
这些都没有真正回答问题,如果我理解正确的话是:
由于 async/await 语法允许您使用非异步样式 try/catch 语法处理被拒绝的 "awaits",为什么不失败"await" 由 Express' try/catch 在顶级处理并为您变成 500?
我相信答案是 Express 内部调用你的任何函数也必须用 "async" 声明并用 "await" 调用你的处理程序以启用异步捕获 try/catch 在该级别工作。
想知道 Express 团队是否有功能请求?他们只需要在两个地方添加两个关键字。如果成功,什么也不做,如果异常移交给错误处理堆栈。