警示后人系列:为什么我没有catch到回调函数中抛出的错误?

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

事情是这样,koa会帮我们捕获中间件中抛出的错误,从而不让服务器崩溃,如下图:
《警示后人系列:为什么我没有catch到回调函数中抛出的错误?》
服务器为此次请求返回500,仍然能够处理后续的请求。

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

但是今天的错误是这样的:
《警示后人系列:为什么我没有catch到回调函数中抛出的错误?》
进程居然崩溃了!这样上线岂不是要出事?

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

《警示后人系列:为什么我没有catch到回调函数中抛出的错误?》
可以看出,内层的try确实捕获到了错误,但是当我们把这个错误继续抛出,外层的try却没有捕获到这个错误

原因在于,回调函数被异步调用时,外层try中的代码其实已经执行完了,栈帧已经从执行栈中弹出。回调函数的栈帧被放入时,执行栈是空的

错误被抛出后,沿着执行栈,希望找到“外层”函数的try…catch语句。可是这个回调函数根本就没有“外层”函数了,因此这是一个没有被捕获到的错误,这会造成进程的崩溃。

总结

用try语句包裹回调函数的定义,无法捕获到回调函数中的错误。必须用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
    原文地址: https://segmentfault.com/a/1190000013635623
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞