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

Promise.race polyfill中then双参与then().catch()行为差异疑问

问题现象

编写Promise.race() polyfill时,最初实现代码如下:

const promise = new Promise((resolve, reject) => {
  resolve('promise resolved');
});

const promise2 = Promise.resolve('promise2 resolved');

function raceCopy(promises) {
  return new Promise(function (resolve, reject) {
    for (var index = 0; index < promises.length; index++) {
        Promise.resolve(promises[index])
          .then(resolve)
          .catch(reject);
    }
  });
}

raceCopy([Promise.reject('copy rejected'), promise, promise2])
  .then((response) => console.log(response))
  .catch((error) => console.error(error));

预期控制台输出错误信息copy rejected,实际输出了promise resolved。
对照验证结果如下:

  • 使用原生Promise.race()执行相同入参,可正确输出copy rejected:
    Promise.race([Promise.reject('copy rejected'), promise, promise2])
     .then((response) => console.log(response))
     .catch((error) => console.error(error));
    
  • 将链式调用的.then(resolve).catch(reject)替换为.then(resolve, reject)后,polyfill运行结果正确:
    function raceCopy(promises) {
      return new Promise(function (resolve, reject) {
        for (var index = 0; index < promises.length; index++) {
          Promise.resolve(promises[index]).then(resolve, reject);
        }
      });
    }
    

两种写法看似逻辑等价,运行结果却完全不同。

核心原理

两种写法完全不等价,差异来自Promise链式调用的两个核心规则:

  1. 每一次.then()/.catch()调用都会返回一个全新的Promise实例,会产生额外的微任务延迟
  2. Promise状态一旦从pending变更为fulfilled/rejected,后续所有对该Promise的resolve/reject调用都会被直接忽略

错误写法的执行流程

逐行拆解.then(resolve).catch(reject)写法的微任务入队、执行顺序:
循环按索引从0到2遍历入参数组,所有回调注册逻辑是同步执行的:

  1. 处理索引0的rejected状态Promise(值为copy rejected):
    • 调用.then(resolve)时,因为当前Promise是rejected,且没有传入onRejected处理函数,JS引擎会生成默认的透传rejection的回调,将这个默认回调加入微任务队列,此时.then()返回的新Promise处于pending状态
    • 后续的.catch(reject)只是给这个pending状态的新Promise注册reject回调,并不会立刻把外层的reject方法加入微任务队列,需要等前一个.then()返回的Promise敲定状态后才会触发
  2. 处理索引1的resolved状态Promise(值为promise resolved):
    • 调用.then(resolve)时,当前Promise是resolved状态,直接把外层resolve回调加入微任务队列
    • 后续的.catch(reject)仅注册错误回调,暂不触发
  3. 处理索引2的resolved状态Promise(值为promise2 resolved):
    • 同索引1的逻辑,把对应的外层resolve回调加入微任务队列,排在索引1的resolve回调之后

同步代码执行完毕,开始按顺序清空微任务队列:

  1. 第一个微任务:执行索引0Promise的默认透传回调,将.then()返回的新Promise置为rejected状态,此时才把外层reject回调推到微任务队列末尾。此时队列顺序为:[resolve('promise resolved'), resolve('promise2 resolved'), reject('copy rejected')]
  2. 第二个微任务:执行外层resolve('promise resolved'),race返回的Promise从pending变为fulfilled,后续所有resolve/reject调用全部失效
  3. 后续两个微任务执行时,因为外层Promise已经敲定状态,调用resolve、reject都不会产生任何效果,最终输出promise resolved

正确写法的执行流程

.then(resolve, reject)是直接在Promise.resolve(promises[index])返回的Promise上同时注册onFulfilled、onRejected两个回调,没有中间的新Promise层,不会产生额外微任务延迟:

  • 处理索引0的rejected Promise时,直接把外层reject回调加入微任务队列,排在队列最前端
  • 清空微任务时,第一个执行的就是reject('copy rejected'),直接把race返回的Promise置为rejected状态,后续的resolve回调全部被忽略。
    这和原生Promise.race的实现逻辑一致:所有传入Promise的状态敲定回调都是直接注册,没有中间层延迟,因此最先敲定的Promise(无论状态是fulfilled还是rejected)会直接决定返回Promise的最终状态,结果符合预期。

额外补充

两种写法的另一个常被忽略的差异是错误捕获范围不同:

  • .then(resolve, reject)中的reject回调,只能捕获当前Promise的rejection,无法捕获resolve回调执行过程中抛出的错误
  • .then(resolve).catch(reject)中的catch回调,不仅能捕获原始Promise的rejection,还能捕获resolve回调执行时抛出的错误,只是这个特性在Promise.race的polyfill场景下,因为额外的微任务延迟导致了顺序错误。

内容的提问来源于stack exchange,提问作者Himanshu singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:15:45