Promise.race手写Polyfill未按预期工作的原因排查
我在手写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));
问题原因
状态完成后新增的回调未被执行
你的MyPromise仅在调用resolve或reject时执行一次executeCallbacks()。如果在Promise状态变为resolved/rejected之后再添加then/catch回调,这些回调会被加入队列,但不会触发执行——因为executeCallbacks()不会被再次调用,且队列执行后会被清空。原生Promise的同步reject导致时序问题
测试用的promise3是原生Promise,它的reject('reject')是同步执行的。这会导致MyPromise.race内部的rej函数立即被调用,此时用户代码中MyPromise.race(...).then(...).catch(...)还未执行完毕,catch回调还没被加入到q队列中。executeCallbacks()执行时队列为空,后续添加的回调自然不会被触发。多余的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

