手写一个PromiseA+

根据PromiseA+规范手写Promise

Posted by wang chong on January 22, 2019

Promise是ES6出现的一个异步编程的一个解决方案,改善了以往回调函数的回调地狱(虽然写起来也挺像的)。不会Promise的可以移步阮一峰的Promise,这里讲的非常清晰。

就现在的发展情况而言,Promise这种解决方案频繁的在我们的代码中出现,当然也成为面试必问的一项,可想而知,理解Promise的实现是非常重要的一点。

本文主要是通过PromiseA+这个规范来一步一步的实现Promise。不了解规范可以看一下下面的规范。

  1. 中文版规范
  2. 英文版规范

从构造函数开始

Promise是一个构造函数,接受一个函数作为参数。从真正的Promise中我们知道,如果接受的不是函数,会报TypeError。

于是我们实现下。

var MyPromise = function(fn){
    if(typeof fn !== 'function'){
        throw new TypeError('Promise resolver undefined is not a function');
    }
}

状态

Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。开始默认状态为pending,只有异步操作的结果才能决定当前是哪一种状态,状态一旦改变,就无法再更改。函数参数接受两个函数作为参数,用于改变状态。我们来实现下。

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
var MyPromise = function(fn){
    const _this = this;
    _this.state = PENDING;
    _this.value = null;     //Promise 的初始终值
    _this.resolve = function(value){
        if(_this.state === PENDING){
            _this.state = RESOLVED;
            _this.value =value;   //终值
        }
    }
    _this.reject = function(value){
        if(_this.state === PENDING){
            _this.state = REJECTED;
            _this.value =value; //据因
        }
    }
}

参数函数自执行

Promise构造函数中的函数参数是自动执行的,并且我们要规避一个问题:当传入的参数抛出异常的情况,如果有直接转成rejected状态。在构造函数中最下面添加代码

try{
    fn(_this.resolve,_this.reject);
}catch(e){
    _this.reject(e);
}

简单实现then方法

then方法接受两个函数作为参数,同时两个参数都是可选的。如果两个参数都不写的话就形成了透传的作用,留给后面的then方法来接参数。then中的参数方法是在promise状态确认之后,并且在状态未确认之前可能会有多个then方法注册,为了保证resolve函数和reject函数的调用时机需要把构造函数中resolve和reject方法修改成异步,于是我们改下MyPromise构造函数。

//添加两个保存回调函数的数组。
_this.resolveCallbacks = [];    //用于保存then方法中resolve
_this.rejectCallbacks = [];     //用于保存then方法中reject
//修改下resolve和reject方法
_this.resolve = function(value){
    //判断参数是不是一个promise
    if(value instanceof MyPromise){
        //如果value是一个promise 递归执行   
        return value.then(_this.resolve,_this.reject);
    }
    //异步执行 保证执行顺序
    setTimeout(() => {
        if(_this.state === PENDING){
            _this.state = RESOLVED;
            _this.value =value;   //终值
            _this.resolveCallbacks.forEach(cb=>cb(_this.value));
        }
    },0)
}
_this.reject = function(value){
    //异步执行 保证执行顺序
    setTimeout(()=>{
        if(_this.state === PENDING){
            _this.state = REJECTED;
            _this.value =value; //据因
            _this.rejectCallbacks.forEach(cb=>(_this.value));
        }  
    },)
}

我们再来写一下then方法

MyPromise.prototype.then = function(onFulfilled,onRejected){
    const _self = this;
    //规范2.2.7.3 /2.2.7.4
    //如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
    //如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
    //如果参数不是函数就忽略,同时实现透传  new Promise().then().then(x=>x)
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};
    
    if(_self.state ===PENDING){
        _self.resolveCallbacks.push(onFulfilled);
        _self.rejectCallbacks.push(onRejected);
    }
    if(_self.state === RESOLVED){
       onFulfilled(_self.value);
    }
    if(_self.state === REJECTED){
        onRejected(_self.value);
    }
}

then方法必须返回一个Promise

规范2.27 Promise的then方法返回一个新的promise。我们来修改一下then方法(先把多余的注释去掉,加上新的注释,最后面会留一个完整的)。

MyPromise.prototype.then = function(onFulfilled,onRejected){
    const _self = this;
    //规范2.2.7
    //promise 的then方法返回一个新的promise
    var promise2;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};
    
    if(_self.state ===PENDING){
        return new MyPromise(function(resolve,reject){
            _self.resolveCallbacks.push(onFulfilled);
            _self.rejectCallbacks.push(onRejected);
        })
    }
    if(_self.state === RESOLVED){
       return new MyPromise(function(resolve,reject){
           onFulfilled(_self.value);
       })
    }
    if(_self.state === REJECTED){
        return new MyPromise(function(resolve,reject){
            onRejected(_self.value);
        })
    }
}

then方法异常处理及兼容调用

在promiseA+规范中的第2.2.7条中我们可以看到这样一些东西。

  1. 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[Resolve]
  2. 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e

解释一下Promise解决过程:解决过程是为了让不同的Promise都可以兼容的使用。比如说jQuery的Promise。

MyPromise.prototype.then = function(onFulfilled,onRejected){
    const _self = this;
    var promise2;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};
    
    if(_self.state ===PENDING){
        return new MyPromise(function(resolve,reject){
            _self.resolveCallbacks.push(function(){
                //规范2.2.7.2
                //如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
                try{
                    var x = onFulfilled(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            });
            _self.rejectCallbacks.push(function(){
                //规范2.2.7.2
                try{
                    var x = onRejected(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            });
        })
    }
    if(_self.state === RESOLVED){
       return new MyPromise(function(resolve,reject){
           //规范2.2.4
            //保证resolve的调用时机
            setTimeout(function(){
                //规范2.2.7.2
                try{
                    var x = onFulfilled(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            },0)
       })
    }
    if(_self.state === REJECTED){
        return new MyPromise(function(resolve,reject){
             //规范2.2.4
            //保证了reject的调用时机
            setTimeout(function(){
                //规范2.2.7.2
                try{
                    var x = onRejected(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            })
        })
    }
}

Promise解决过程的实现

规范上解决过程函数参数严格的限制[Resolve]

function resolutionProcedure(promise2,x,resolve,reject){
    
}
避免promise2与x指向同一对象

如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise,避免循环引用。

function resolutionProcedure(promise2,x,resolve,reject){
    if(promise2 === x){
        return reject(new TypeError('循环引用'));
    }
}
当x是一个Promise的时候

如果x是一个Promise

  1. 如果x处于等待态,promise需保持等待态直至x被拒绝或执行
  2. 如果x处于执行态,用相同的值执行promise
  3. 如果x处于拒绝态,用相同的据因拒绝promise

在resolutionProcedure中添加代码

if(x instanceof MyPromise){
    //如果x处于等待太,promise需保持等待态直至x被拒绝或执行
    if(x.state === PENDING){
        x.then(function(value){
            //再次调用该函数是为了确认x resolve的参数是什么类型,如果是基本类型就再次resolve传入下一个then
            resolutionProcedure(promise2,value,resolve,reject);
        },reject)
    }else{
        //如果x处于执行态,用相同的值执行promise
        //如果x处于拒绝态,用相同的据因拒绝promise
        x.then(resolve,reject);
    }
    return;
}
当x为对象或者函数的时候

根据规范我们可以得出,当resolve和reject成功执行之后就会忽略掉未执行的一方。在resolutionProcedure中加入一下代码

//规范2.3.3
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
    
    //规范2.3.3.2
    //如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    try{
        //规范2.3.3.1
        //把 x.then 赋值给 then
        var then =x.then;
        
        if(typeof then === 'function'){
            //规范2.3.3.3
            //如果 then 是函数,将 x 作为函数的作用域 this 调用之。
            //传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
            then.call(
                x,
                //规范2.3.3.3.1
                //如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                y => {
                    if(called){
                        return ;
                    }
                    called = true;
                    resolutionProcedure(promise2,y,resolve,reject);
                },
                //规范2.3.3.3.2
                //如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                r =>{
                    if(called){
                        return ;
                    }
                    called = true;
                    resolutionProcedure(promise2,r,resolve,reject);
                }
            );
        }else{
            //规范2.3.3.3.4
            //如果 then 不是函数,以 x 为参数执行 promise
            resolve(x);
        }
    }catch(e){
        //规范2.3.3.3.4
        if(called){
            return;
        }
        called = true;
        reject(e);
    }
}else{
    //规范2.3.4
    //如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x);
}

至此所有PromiseA+规范的东西已经全部实现完了,下面贴出全部加上规范注释的代码。

PromiseA+规范实现

var MyPromise = function(fn){
    //判断构造函数的参数是否为函数
    if(typeof fn !== 'function'){
        throw new TypeError('Promise resolver undefined is not a function');
    }
    const _this = this;
    //promise的状态
    _this.state = PENDING;
    //promise的值 
    _this.value = null;
    //用于保存then中的回调,当状态为pending的时候才会缓存,并且每个实例至多缓存一次。
    _this.resolveCallbacks = [];
    _this.rejectCallbacks = [];
    
    //resolve的方法
    _this.resolve = function(value){
        //判断参数是不是一个promise
        if(value instanceof MyPromise){
            //如果value是一个promise 递归执行   
            return value.then(_this.resolve,_this.reject);
        }
        //异步执行
        setTimeout(function(){
            if(_this.state === PENDING){
                _this.state = RESOLVED;
                _this.value = value;
                _this.resolveCallbacks.forEach(cb => cb(_this.value));
            }
        },0)
    }
    //reject的方法
    _this.reject = function(value){
        //异步执行 保证执行顺序
        setTimeout(function(){
            if(_this.state === PENDING){
                _this.state = REJECTED;
                _this.value = value;
                _this.rejectCallbacks.forEach(cb => cb(_this.value));
            }
        },0)

    }

    //用于解决构造函数传入的函数返回出一个异常的情况
    //new MyPromise(()=> throw new Error('error'))
    try{
        fn(_this.resolve,_this.reject);
    }catch(e){
        _this.reject(e);
    }
}

MyPromise.prototype.then = function(onFulfilled,onRejected){
    const _self = this;
    //规范2.2.7
    //promise 的then方法返回一个新的promise
    var promise2;
    //规范2.2.7.3 /2.2.7.4
    //如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
    //如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
    //如果参数不是函数就忽略,同时实现透传  new Promise().then().then(x=>x)
    onResolved = typeof onResolved === 'function' ? onResolved : x => x;
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};
    if(_self.state === PENDING){
        return promise2 = new MyPromise(function(resolve,reject){
            _self.resolveCallbacks.push(function(){
                //规范2.2.7.2
                //如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
                try{
                    var x = onFulfilled(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            });
            _self.rejectCallbacks.push(function(){
                //规范2.2.7.2
                try{
                    var x = onRejected(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            })
        })
    }
    if(_self.state === RESOLVED){
        return promise2 = new MyPromise(function(resolve,reject){
            //规范2.2.4
            //保证resolve的调用时机
            setTimeout(function(){
                //规范2.2.7.2
                try{
                    var x = onFulfilled(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            },0)
        })
    }
    if(_self.state === REJECTED){
        return promise2 = new MyPromise(function(resolve,reject){
            //规范2.2.4
            //保证了reject的调用时机
            setTimeout(function(){
                //规范2.2.7.2
                try{
                    var x = onRejected(_self.value);
                    //规范2.2.7.1
                    resolutionProcedure(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            })
        })
    }
}
//规范2.3
function resolutionProcedure(promise2,x,resolve,reject){
    //规范2.3.1
    //如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise,避免循环引用
    if(promise2 === x){
        return reject(new TypeError('循环引用'));
    }
    //规范2.3.2
    //如果x为promise
    if(x instanceof MyPromise){
        //如果x处于等待太,promise需保持等待态直至x被拒绝或执行
        if(x.state === PENDING){
            x.then(function(value){
                //再次调用该函数是为了确认x resolve的参数是什么类型,如果是基本类型就再次resolve传入下一个then
                resolutionProcedure(promise2,value,resolve,reject);
            },reject)
        }else{
            //如果x处于执行态,用相同的值执行promise
            //如果x处于拒绝态,用相同的据因拒绝promise
            x.then(resolve,reject);
        }
        return;
    }
    //规范2.3.3.3.3
    //如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    var called = false;
    //规范2.3.3
    if(x !== null && (typeof x === 'object' || typeof x === 'function')){
        
        //规范2.3.3.2
        //如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
        try{
            //规范2.3.3.1
            //把 x.then 赋值给 then
            var then =x.then;
            
            if(typeof then === 'function'){
                //规范2.3.3.3
                //如果 then 是函数,将 x 作为函数的作用域 this 调用之。
                //传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
                then.call(
                    x,
                    //规范2.3.3.3.1
                    //如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                    y => {
                        if(called){
                            return ;
                        }
                        called = true;
                        resolutionProcedure(promise2,y,resolve,reject);
                    },
                    //规范2.3.3.3.2
                    //如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    r =>{
                        if(called){
                            return ;
                        }
                        called = true;
                        resolutionProcedure(promise2,r,resolve,reject);
                    }
                );
            }else{
                //规范2.3.3.3.4
                //如果 then 不是函数,以 x 为参数执行 promise
                resolve(x);
            }
        }catch(e){
            //规范2.3.3.3.4
            if(called){
                return;
            }
            called = true;
            reject(e);
        }
    }else{
        //规范2.3.4
        //如果 x 不为对象或者函数,以 x 为参数执行 promise
        resolve(x);
    }
}