4

今天犯了一个js中的错误,记录一下警示后人:)

事情是这样,koa会帮我们捕获中间件中抛出的错误,从而不让服务器崩溃,如下图:
clipboard.png
服务器为此次请求返回500,仍然能够处理后续的请求。

从这一点出发,我就认为,我可以在中间件里面随便throw,这样koa就能在控制台帮我打印出所有错误的信息,反正也不会对后续请求造成影响。

但是今天的错误是这样的:
clipboard.png
进程居然崩溃了!这样上线岂不是要出事?

为什么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
}

clipboard.png
可以看出,内层的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语句早已经执行完。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.