You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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链式调用的新实例生成逻辑,以及微任务队列的入队顺序:

  1. Promise链的每一步都是新实例
    不管是then还是catch,每次调用都会返回一个全新的Promise。后续的回调都是挂载在这个新实例上的,而不是原来的Promise。

  2. 两种场景的微任务入队差异

场景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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 02:20:18