今天犯了一个js中的错误,记录一下警示后人:)
事情是这样,koa会帮我们捕获中间件中抛出的错误,从而不让服务器崩溃,如下图:
服务器为此次请求返回500,仍然能够处理后续的请求。
从这一点出发,我就认为,我可以在中间件里面随便throw,这样koa就能在控制台帮我打印出所有错误的信息,反正也不会对后续请求造成影响。
但是今天的错误是这样的:
进程居然崩溃了!这样上线岂不是要出事?
为什么koa这回没有捕获到我throw的错误呢?
用简单的代码模拟一下这个场景:
try {
setTimeout(() => {
try {
throw 500;
} catch (err) {
console.log('err1', err); // called
throw err;
}
}, 0);
} catch (err) {
console.log('err2', err) // never called
}
可以看出,内层的try确实捕获到了错误,但是当我们把这个错误继续抛出,外层的try却没有捕获到这个错误。
原因在于,回调函数被异步调用时,外层try中的代码其实已经执行完了,栈帧已经从执行栈中弹出。当定时器时间到达时,执行引擎将回调函数压入一个空栈,也就是说回调函数处于执行栈的底部。如果这个回调函数没有catch住某个错误,那么这个错误就会泄露到全局。在node中,全局收到错误会造成进程的崩溃。
总结
错误的被抛出的时候是沿着执行栈向上找handler的。运行时的执行栈结构,与程序员看到的“代码块结构”往往是不同的:用try语句包裹回调函数的定义,无法捕获到回调函数中的错误。
如果回调函数运行时没有外层函数,你必须在回调函数内部做错误的捕获和处理。
如果回调函数定义在Promise中,你可以直接在回调函数中调用reject(reason),让这个Promise的订阅者来处理错误:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw 500;
} catch (err) {
console.log('err1', err); // called
reject(err);
}
}, 0);
});
p.catch(err => {
console.log('err3', err); // called
});
如果回调函数由async function的await
关键字来执行,那么可以通过reject Promise,让async function中的try...catch捕获到错误。
async function fun() {
try {
await new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw 500;
} catch (err) {
console.log('err1', err); // called
reject(err);
}
}, 0);
});
} catch (err) {
console.log('err4', err); // called
throw err;
}
}
try {
fun();
} catch (err) {
console.log('err5', err); // not called
}
注意try {fun();}
无法捕获到错误,因为这个函数不是通过await
来执行的。错误抛出的时候,这个try语句早已经执行完。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。