异常捕获

ES5 中的传统做法

假设代码块执行抛出错误 fail,那么捕获该错误的写法为:

try {
  // 代码块执行,并抛出 fail 错误
  throw new Error('fail');
} catch (e) {
  console.log(e);
}

定时器

我们先来针对上面的代码改写一下,加入一个定时器。

try {
  setTimeout(() => {
    throw new Error('fail');
    // Uncaught Error: fail
  }, 1000);
} catch (e) {
  console.log(e);
}

像这样,将 try/catch 扔在定时器的外面,是无法捕获到内部的错误的。

正确的做法应该是:

setTimeout(() => {
  try {
    throw new Error('fail');
  } catch (e) {
    console.log(e);
  }
}, 1000);

Promise

function doSomething() {
  return new Promise((resolve, reject) => {
    // 同步代码中的 throw 可以被捕捉到
    throw new Error('fail');
  });
}

doSomething()
  .then((x) => {
    console.log('success:', x);
  })
  .catch((err) => {
    console.log('fail:', err);
  });

这样写是没有问题的,错误能够被捕获到。但只要稍微修改一下,可能就出现问题了。比如:

function doSomething() {
  return new Promise((resolve, reject) => {
    // 异步代码中的 throw 不能被 Promise 的 catch 捕捉到
    setTimeout(() => {
      throw new Error('fail');
    }, 1000);
  });
}

doSomething()
  .then((x) => {
    console.log('success:', x);
  })
  .catch((err) => {
    console.log('fail:', err);
  });

这里抛出但错误将不能被捕获。所以,在 Promise 中,我们一般通过 reject 来抛出错误。

function doSomething(x) {
  return new Promise((resolve, reject) => reject(x));
}

doSomething('fail')
  .then((x) => {
    console.log('success:', x);
  })
  .catch((err) => {
    console.log('fail:', err);
  });
// fail: fail

另外,还有一个比较有意思的细节,在 catch 之后继续添加 .then 会被继续执行。

function doSomething(x) {
  return new Promise((resolve, reject) => reject(x));
}

doSomething('fail')
  .then((x) => {
    console.log('success:', x);
  })
  .catch((err) => {
    console.log('fail:', err);
    // 这里可以写 return 给下面的方法继续执行
  })
  .then((x) => {
    console.log('continue:', x);
  });
// fail: fail
// continue: undefined

Async/Await

本质上来讲, Async/Await 是通过 Promise 实现,所以基本跟上面 Promise 所讲的差不多。

可以在 await 方法外嵌套 try/catch,类似这样:

function doSomething(x) {
  return new Promise((resolve, reject) => reject(x));
}

(async () => {
  try {
    const result = await doSomething('fail');
    console.log('success:', result);
    // return 返回
  } catch (err) {
    console.log('fail:', err);
    // return 返回
  }
})();
// fail: fail

但这里就有一个问题,比如函数需要有返回,那么返回的语句就需要写两次,正常但时候返回结果,错误的时候,返回一个 throw new Error() 或者其他的。有一个小的窍门,可以这样写:

function doSomething(x) {
  return new Promise((resolve, reject) => reject(x));
}

(async () => {
  const result = await doSomething('fail').catch((err) => {
    console.log('fail:', err);
    return 0; // 默认值
  });
  console.log('success:', result);
})();
// fail: fail
// success: 0

在错误捕获到之后,重新分配一个默认值,让代码继续运行。

在 GitHub 上编辑本页面 更新时间: Mon, Apr 10, 2023