JavaScript中Promise的catch()行为困惑:移除与保留的执行差异
为何Promise链中未执行的catch()会改变执行顺序?
我有个疑问:移除Promise(p2)的catch()后执行结果符合预期,保留时执行流程却发生变化?且此时p2的catch()并未执行,想请教大家这是为什么。
移除Promise(p2)的catch()
const onFinally_1 = () => { console.log('Promise settled! 1'); i++; }; const onFinally_3 = () => console.log('Promise settled! 3'); const p1 = new Promise((resolve, reject) => { resolve(1); }) .then( (res) => Promise.resolve(onFinally_1()).then(() => res), (err) => Promise.resolve(onFinally_1()).then(() => { throw err; }) ) .then((result) => console.log(result), (x) => { console.log('B'); throw x; }) .catch((error) => console.log('=>', error)) const p2 = new Promise((resolve, reject) => { resolve(3); }) .then((result) => console.log(result)) // .catch((error) => console.log(error)) .then( (res) => Promise.resolve(onFinally_3()).then(() => res), (err) => Promise.resolve(onFinally_3()).then(() => { throw err; }) )
执行结果:
Promise settled! 1 3 B Promise settled! 3 ReferenceError: i is not defined...
保留Promise(p2)的catch()
const onFinally_1 = () => { console.log('Promise settled! 1'); i++; }; const onFinally_3 = () => console.log('Promise settled! 3'); const p1 = new Promise((resolve, reject) => { resolve(1); }) .then( (res) => Promise.resolve(onFinally_1()).then(() => res), (err) => Promise.resolve(onFinally_1()).then(() => { throw err; }) ) .then((result) => console.log(result), (x) => { console.log('B'); throw x; }) .catch((error) => console.log(error)) const p2 = new Promise((resolve, reject) => { resolve(3); }) .then((result) => console.log(result)) .catch((error) => console.log(error)) .then( (res) => Promise.resolve(onFinally_3()).then(() => res), (err) => Promise.resolve(onFinally_3()).then(() => { throw err; }) )
执行结果:
Promise settled! 1 3 B ReferenceError: i is not defined... Promise settled! 3
问题解析
核心在于Promise链式调用的新实例生成逻辑,以及微任务队列的入队顺序:
Promise链的每一步都是新实例
不管是then还是catch,每次调用都会返回一个全新的Promise。后续的回调都是挂载在这个新实例上的,而不是原来的Promise。两种场景的微任务入队差异
场景1:移除p2的catch()
p2的链是:resolve(3) → then(打印3) → then(执行onFinally_3)
- 第一个
then执行完后,返回一个已完成(fulfilled)的Promise(因为console.log(3)没有返回值,默认返回undefined,所以这个then的Promise状态是完成)。 - 这个完成状态的Promise会立即触发后续
then的成功回调,把onFinally_3相关的逻辑加入微任务队列。 - 此时p1链的进度是:
onFinally_1执行抛出错误 → 触发第二个then的错误回调(打印'B')→ 重新抛出错误 →catch回调加入微任务队列。 - 微任务队列的顺序是:p2的第二个
then回调 → p1的catch回调,所以先打印Promise settled!3,再打印错误。
场景2:保留p2的catch()
p2的链变成:resolve(3) → then(打印3) → catch(...) → then(执行onFinally_3)
catch本质是then(undefined, 错误处理函数),当前面的Promise是完成状态时,catch不会执行错误回调,但会返回一个新的完成状态Promise,传递前面的决议值。- 关键差异在这里:p2的第二个
then是挂载在catch返回的新Promise上的,这个新Promise的决议时机虽然和前一个then的Promise几乎同步,但微任务队列的入队顺序被改变了:
p1链中,抛出错误后,catch回调会先于p2链中catch之后的then回调入队。所以先执行p1的catch打印错误,再执行p2的then打印Promise settled!3。
简单总结:加了catch后,p2的后续then回调入队时机被推迟,晚于p1的catch回调,导致执行顺序颠倒。
内容的提问来源于stack exchange,提问作者together learning
相关产品推荐
相关产品推荐

