async函数的异常处理本质上是Promise链上的错误传递机制。无论是同步代码抛出异常,还是await一个被拒绝的Promise,最终都会导致async函数返回的Promise进入rejected状态。简单来说,在async函数内部使用throw等价于返回Promise.reject()。

理解async函数的异常处理,首先需要明确一个关键点:它并没有自动捕获异常的能力。无论是同步代码中抛出的异常,还是await一个被拒绝的Promise,最终都会转化为该函数返回的Promise的rejection状态,并沿着Promise链向下传递。
同步代码抛出异常 → 返回Promise被rejected
在async函数中,如果同步代码(例如await之前或未被try/catch包裹的语句)抛出异常,函数执行将立即终止,并导致该async函数返回的Promise直接进入rejected状态,后续代码不再执行。
举个例子:
async function foo() {
throw new Error('boom');
console.log('never runs'); // 不会执行
}
foo().catch(err => console.log(err.message)); // 'boom'
可见,throw语句一执行,后面的console.log永远不会被执行。
await一个rejected Promise → 等效于throw语句
如果await后面跟了一个被拒绝的Promise,JavaScript引擎会自动将其解包,并以throw的形式将该Promise的拒绝原因(reason)重新抛出,其行为与同步throw完全一致。这个错误要么被上层try/catch捕获,要么继续向上冒泡,直到被Promise链上的catch捕获。
例如:
async function bar() {
try {
await Promise.reject('failed');
} catch (e) {
console.log('caught:', e); // 'caught: failed'
throw e; // 二次抛出 —— 注意:这不是“新抛”,而是延续 rejection 流程
}
}
bar().catch(e => console.log('outer:', e)); // 'outer: failed'
这里的catch块中的throw e,并不是重新创建一个新异常,而是将已经捕获的错误继续沿着Promise链向下传递。
二次抛出的本质:延续Promise rejection链
在async函数中,只要使用了throw(无论是否在catch块内),都会导致当前async函数返回的Promise变为rejected状态,其reason就是throw的值。这并不是什么“重新触发异常”的玄学,而是构造一个新的rejected Promise并返回。
- throw在async内部 = 返回Promise.reject(value)
- 未被捕获的await rejection = 自动转为throw,再变成Promise.reject(reason)
- 在try/catch中throw,只是提前结束当前async执行,并将控制权交还给调用方的Promise链
常见误区:不是“异常重抛”,而是“Promise状态传递”
很多人误以为“二次抛出”是JavaScript异常机制在底层做了特殊处理,其实并非如此。async/await本质上是Promise的语法糖,其底层的错误处理依然是Promise的那一套。所谓的“二次抛出”,不过是开发者主动在catch块中选择不消化错误,而是通过throw让外层Promise继续维持rejected状态。在这个过程中,并没有发生异常栈的“再抛”,也不会改变原始错误对象(除非显式地new了一个新Error)。
因此,核心要点是:async函数永远返回一个Promise;其异常处理逻辑本质上就是Promise的rejection传递逻辑,没有更多复杂之处。
