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

Promise.race手写Polyfill未按预期工作的原因排查

手写Promise Polyfill中Promise.race方法未触发catch回调的问题

我在手写Promise的Polyfill时,发现MyPromise.race()方法表现不符合预期。测试代码里本该输出reject的结果,但实际控制台无任何输出,调试发现代码从未进入MyPromise.race()的catch回调,请问这是为什么?

class MyPromise{
    q;
    resolutionState; // normal, error
    currVal;
    status; // resolved,rejected,pending
    constructor(callback){
        this.q = []
        this.currVal = null
        this.status = 'pending'
        this.resolutionState = 'normal'
        this.currKind = null
        setTimeout(()=>{
            callback(this.resolve.bind(this),this.reject.bind(this))
        })
    }

    then(callback){
        this.q.push({type:'then',callback})
        return this
    }

    catch(callback){
        this.q.push({type:'catch',callback})
        return this
    }

    executeCallbacks(){
        let i = 0;
        while(i<this.q.length){
            if(this.resolutionState == 'normal'){
                if(this.q[i]['type'] === 'then'){
                    try {
                        this.currVal = this.q[i]['callback'](this.currVal)
                        i++
                    } catch (error) {
                        this.resolutionState = 'error'
                        this.currVal = error
                        i++;
                    }
                }
                else{
                    i++;
                }
            }
            else{
                if(this.q[i]['type'] === 'catch'){
                    this.currVal = this.q[i]['callback'](this.currVal)
                    if(this.currVal)
                        this.resolutionState = 'normal'
                    i++
                }
                else{
                    i++;
                }
            }
        } 
        this.q = []
    }

    resolve(arg){
        if(arg!=undefined)
            this.currVal = arg
        this.resolutionState = 'normal'
        this.status = 'resolved'
        this.executeCallbacks()
        return this
    }

    reject(arg){
        if(arg!=undefined)
            this.currVal = arg
        this.resolutionState = 'error'
        this.status = 'rejected'
        this.executeCallbacks()
        return this
    }

    static async race(promises){
          return new MyPromise((res,rej)=>{
                promises.forEach(promise => {
                    try {
                        if(promise instanceof MyPromise || promise instanceof Promise){
                            promise.then(res).catch(rej)
                        }
                        else{
                            res(promise)
                        }
                    } catch (error) {
                        rej(error)
                    }
                })
            })
    }
}

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'two');
});

const promise3 = new Promise((resolve,reject)=>{
    reject('reject')
})

MyPromise.race([promise1, promise2, promise3]).then((value) => {
    console.log(value);
})
.catch(err => console.log(err));

问题原因

  1. 状态完成后新增的回调未被执行
    你的MyPromise仅在调用resolve或reject时执行一次executeCallbacks()。如果在Promise状态变为resolved/rejected之后再添加then/catch回调,这些回调会被加入队列,但不会触发执行——因为executeCallbacks()不会被再次调用,且队列执行后会被清空。

  2. 原生Promise的同步reject导致时序问题
    测试用的promise3是原生Promise,它的reject('reject')是同步执行的。这会导致MyPromise.race内部的rej函数立即被调用,此时用户代码中MyPromise.race(...).then(...).catch(...)还未执行完毕,catch回调还没被加入到q队列中。executeCallbacks()执行时队列为空,后续添加的回调自然不会被触发。

  3. 多余的async修饰符
    MyPromise.race方法不需要async修饰,它直接返回MyPromise实例即可,async会导致额外的Promise包装,干扰预期逻辑。


修复方案

1. 让已完成状态的Promise能处理新增回调

修改then和catch方法,当Promise状态不是pending时,立即触发回调执行:

then(callback){
    this.q.push({type:'then',callback})
    if(this.status !== 'pending'){
        this.executeCallbacks()
    }
    return this
}

catch(callback){
    this.q.push({type:'catch',callback})
    if(this.status !== 'pending'){
        this.executeCallbacks()
    }
    return this
}

2. 移除race方法的async修饰符

static race(promises){
    return new MyPromise((res,rej)=>{
        promises.forEach(promise => {
            try {
                if(promise instanceof MyPromise || promise instanceof Promise){
                    promise.then(res).catch(rej)
                }
                else{
                    res(promise)
                }
            } catch (error) {
                rej(error)
            }
        })
    })
}

3. 修复构造函数的executor执行时机(可选,符合Promise规范)

原生Promise的executor是同步执行的,你当前用setTimeout包裹会导致时序偏差,可移除:

constructor(callback){
    this.q = []
    this.currVal = null
    this.status = 'pending'
    this.resolutionState = 'normal'
    this.currKind = null
    // 移除setTimeout,同步执行executor
    callback(this.resolve.bind(this),this.reject.bind(this))
}

修复后,MyPromise.race会正确触发catch回调,控制台输出reject。


内容的提问来源于stack exchange,提问作者Daksh Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:27:09