Promise的實現及解析

WZZ發表於2018-03-23

Promise雛形(加入狀態機)

function Promise(executor){

    let self = this;
    self.status = 'pending'; 
    self.value = undefined;
    self.reason = undefined;
    function resolve(value){
        if( self.status === 'pending'){
            self.status = 'fulfilled';
            self.value = value;
        }
    }
    function reject(reason){
        if( self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}
Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this;
    if(self.status === 'fulfilled'){
        onFulfilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise;
複製程式碼

測試用例1

let Promise = require('Mypromise');

let promise = newPromise(function(resolve,reject){
        resolve(results)
})
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
})
複製程式碼

首先我們定義一個status狀態變數,預設Promise處於pending狀態

promise接收一個executor函式,該函式在構造promise例項時就執行,所以在Promise構造方法中執行executor,executor需要接收resolve作為執行成功時的回撥函式,接收reject作為執行失敗時的回撥函式,所以定義了resolve和reject方法

resolve接收執行成功的返回結果作為引數
reject 接收執行失敗的返回結果作為引數
所以這裡定義了value表示成功結果,reason表示錯誤原因
呼叫resolve或reject後,會讓promise進入filfilled成功狀態rejected失敗狀態,並且只有promise為在pending狀態下,才能切換到成功/失敗態

promise例項需要用then方法註冊執行成功/失敗的回撥方法,then中根據promise所處狀態,判斷呼叫成功還是失敗的回撥
這樣一個簡單的promise的實現就寫好了

加入非同步回撥處理,支援註冊多個then

function Promise(executor){ 
    let self = this;
    self.status = 'pending'; 
    self.value = undefined;
    self.reason = undefined;
    self.onResolvedCallbacks = [];
    self.onRejectedCallbacks = [];
    
    function resolve(value){
        if( self.status === 'pending'){
            self.status = 'fulfilled'; 
            self.value = value; 
            self.onResolvedCallbacks.forEach(function(fn){
                fn();
            })
        }
    }
    
    function reject(reason){
        if( self.status === 'pending'){//只能從pending狀態切換到rejected狀態
            self.status = 'rejected';
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function(fn){
                fn();
            })
        }
    }
    executor(resolve,reject);
}

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this;
    if(self.status === 'fulfilled'){
        onFulfilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
   
    if(self.status === 'pending'){
        self.onResolvedCallbacks.push( function(){
            onFulfilled(self.value)
        });
        self.onRejectedCallbacks.push( function(){
            onRejected(self.reason)
        });
    }
}
module.exports = Promise;
複製程式碼

測試用例2

let promise = new Promise(function(resolve,reject){
    http.get(url, function(results) {
        resolve(results)
    })
})
promise.then(function(data){
    console.log('data',data);
},function(err){
    console.log('err',err);
})
promise.then(function(data){
    console.log('data',data);
},function(err){
    console.log('err',err);
})
promise.then(function(data){
    console.log('data',data);
},function(err){
    console.log('err',err);
})
複製程式碼

promise主要用於處理非同步回撥的情況,上例中發起http請求,請求成功後,用resolve發起成功的回撥;並呼叫多次then註冊了多個成功、失敗的回撥方法;如果執行成功,原生的promise會將每個then的成功回撥都執行一遍

由於非同步的請求,當呼叫then時,promise還處於pending狀態,所以我們需要將then註冊的回撥方法暫存,以便成功或失敗時回撥,為此定義 onResolvedCallbacks onRejectedCallbacks 分別存放then註冊的成功、失敗回撥方法,上例所示, 可能多次呼叫then註冊,所以onResolvedCallbacks =[],是個陣列
當執行成功會呼叫resolve,那我們在實現resolve方法時,將所有then中的成功回撥都呼叫一遍,就是這段程式碼

self.onResolvedCallbacks.forEach(function(fn){
    fn();
})
複製程式碼

executor異常處理

當執行非同步操作時有可能發生異常,需要try/catch捕獲到異常,並使promise進入rejected狀態

try {
    executor(resolve,reject); //捕獲的時候發生異常,執行reject
} catch (error) {
    reject(error)
}
複製程式碼

可以在executor中丟擲throw new Error('error')測試

鏈式回撥的異常處理

then中無論是執行成功的回撥還是失敗回撥,只要有返回結果,都會走到下一個then(根據不同返回結果進入下一個then的不同回撥,規則我的另一偏文章juejin.im/post/5aae65…

promise是通過then中返回新的promise來實現鏈式呼叫的,試想:一個新的promise是可以繼續呼叫then方法的,補充then方法如下

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this;
    let promise2; //then返回的新Promise
    if(self.status === 'fulfilled'){
        //onFilfilled會同步執行,並返回新的promise2
        promise2 = new Promise(function (resolve,reject) {
            onFulfilled(self.value);
        });
    }
    if(self.status === 'rejected'){
        promise2 = new Promise(function (resolve,reject) {
            onRejected(self.reason);
        });
    }

    if(self.status === 'pending'){
        promise2 = new Promise(function (resolve,reject) {
            self.onResolvedCallbacks.push( function(){
            //捕獲非同步回撥時的異常,如果有進入promise2的失敗態
                 try{
                    onFulfilled(self.value);
                }catch (e){
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push( function(){
                 try{
                    onRejected(self.reason);
                }catch (e){
                    reject(e);
                }
            });
        });
    }
    return promise2;
}
複製程式碼

上面程式碼第一個if語句,執行成功回撥時,返回新的promise2,因為new promise時,executor會立即執行,所以onFulfilled(成功回撥)方法會同步執行,並捕獲其中的異常;第二個if語句失敗回撥同理。
第三個if語句,如果是非同步回撥,執行promise2的executor方法時,只是將這個成功的回撥onFulfilled加入成功回撥佇列onResolvedCallbacks(失敗同理),當成功回撥真正執行時,如果發生異常,還需要捕獲,並進入新promise2的失敗態

鏈式回撥的返回值處理

如果第一個promise返回一個普通值,會走到下一次then的成功的回撥
如果第一個promise返回了一個promise,需要等待返回的promise執行後的結果,再傳遞給下一次then中。
所以我們用x接收第一個then的返回值 let x = onFulfilled(self.value);
x可能是普通值,也可能是promise,也可能是別人實現的promise, 這裡實現一個resolvePromise方法統一處理返回值

then的程式碼更新如下 : 都用x接收回撥函式的返回值,並呼叫resolvePromise來處理

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this;
    let promise2;//then返回的新Promise
    if(self.status === 'fulfilled'){
        promise2 = new Promise(function (resolve,reject) {
               let x= onFulfilled(self.value);
               resolvePromise(promise2,x,resolve,reject);
        });
    }
    if(self.status === 'rejected'){
        promise2 = new Promise(function (resolve,reject) {
                let x= onRejected(self.reason);
                resolvePromise(promise2,x,resolve,reject);
           
        });
    }
    if(self.status === 'pending'){
        promise2 = new Promise(function (resolve,reject) {
            self.onResolvedCallbacks.push( function(){
                 try{
                    let x= onFulfilled(self.value);
                    resolvePromise(promise2,x,resolve,reject);
                }catch (e){
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push( function(){
                 try{
                    let x= onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                }catch (e){
                    reject(e);
                }
            });
        });
    }
    return promise2;
}
複製程式碼

resolvePromise的實現如下
引數:
p2 :第二次then的promise例項
x:第一次then的返回值
resolve/reject : p2的 resolve/reject

function resolvePromise(p2,x,resolve,reject){
   if(p2 === x){ //報一個型別錯誤
        return reject(new TypeError('迴圈引用了'));
   }
   //判斷x是不是promise
   if(x!== null || (typeof x === 'object'||typeof x === 'function')){
        //x可能是promise   看物件中是否有then方法,有then就認為是promise
        //取then方法有可能異常,發生異常就進入p2的失敗態
        try {
            let then = x.then;
            if(typeof then === 'function'){ 
                //認為x是一個promise,立刻執行該promise的then方法
                //如果x進入成功態,會觸發成功回撥
                then.call(x,function(y){ 
                    //y可能還是一個promise,繼續解析,直到返回的是一個普通值
                    resolvePromise(p2,y,resolve,reject);
                    
                },function(err){ //如果x進入失敗態,會觸發p2的失敗態
                    reject(err);
                });

            }else{ //如果then不是方法,直接認為返回一個物件,呼叫p2成功態
                resolve(x);
            }
        } catch (error) {
            reject(error);
        }
        
   }else{ //x是普通值,呼叫p2成功態
        resolve(x);
   }
};
複製程式碼

多次呼叫resolve,reject 加入called標識

如果有人把程式碼寫成

let p1= new Promise(function (resolve,reject) {
  resolve('success');
  reject('fail1');
});
複製程式碼

promise的處理方式是,一旦進入成功態,就成功了,不會再呼叫reject,反之亦然
這裡通過在resolvePromise方法中,加入called 標識,表示已經進入一個resolve或reject;若果called為true,直接返回,如果為false,置為true

function resolvePromise(p2, x, resolve, reject) {
    if (p2 === x) { //這裡應該報一個型別錯誤,有問題
        return reject(new TypeError('迴圈引用了'))
    }
    let called; // 表示是否呼叫過成功或者失敗
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try { 
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return;
                    called = true;
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}
複製程式碼

Promise中值的穿透

我們可以在then中什麼都不寫

p1.then().then().then(function(data){
    console.log('data',data);
},function(err){
    console.log('err',err);
})
複製程式碼

p1的執行結果依然會穿透到最後一個then的相應的回撥
這需要在then方法中一開始就判斷,是否有resolve/reject方法,如果沒有,需要給預設的處理方法

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : 
    function(value){ //預設的成功回撥,返回一個值,就會進入下一個then的成功回撥
      return value;
    };
onRejected = typeof onRejected === 'function' ? onRejected : 
    function(err){//預設的失敗回撥,丟擲異常,就會進入下一個then的失敗回撥
        throw err;
    };
複製程式碼

實現catch

捕獲錯誤的方法,catch就相當於一個then呼叫錯誤方法

Promise.prototype.catch = function (callback) {
    return this.then(null,callback);
}
複製程式碼

實現Promise.all

all接收一個成員為promise例項的陣列,依次執行,並按順序返回執行結果
當所有promise都執行成功,就進入成功態,有一個執行失敗了,就進入失敗態

 //promises是一個promise的陣列
Promise.all = function (promises) {
    return new Promise(function (resolve, reject) {
        let arr = []; //arr是最終返回值的結果
        let count = 0; // 表示成功了多少次
        function processData(index, y) {
            arr[index] = y;
            if (++count === promises.length) {
                resolve(arr);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(function (y) {
                processData(i, y)
            }, reject) //有一個失敗,就呼叫失敗回撥
        }
    })
};
複製程式碼

實現Promise.race

引數同all,只要有一個promise成功了,就算成功。如果有一個失敗了,就失敗了,其他promise繼續執行

Promise.race = function (promises) {
    return new Promise(function (resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve,reject)
        }
    })
};
複製程式碼

實現Promise.resolve/Promise.reject

Promise.resolve 可以理解為 生成一個成功的promise

Promise.resolve = function(value){
    return new Promise(function(resolve,reject){
        resolve(value);
    })
}
複製程式碼

Promise.reject 即生成一個失敗的promise

Promise.reject = function(reason){
    return new Promise(function(resolve,reject){
        reject(reason);
    })
}
複製程式碼

Promises/A+規範明確要求回撥需要通過非同步方式執行,用以保證一致可靠的執行順序

這裡用setTimeout模擬非同步執行,將所有成功和失敗的回撥都用setTimeout包裝,既然非同步執行,還需捕獲異常

最後Promise的實現如下(總)

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    function resolve(value) { // 成功狀態
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }
    function reject(reason) { // 失敗狀態
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }
    try {
        executor(resolve, reject)
    } catch (e) { // 捕獲的時候發生異常,就直接失敗了
        reject(e);
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    // 有可能這裡返回的x是別人的promise
    // 儘可能允許其他亂寫
    if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
        return reject(new TypeError('迴圈引用了'))
    }
    // 看x是不是一個promise,promise應該是一個物件
    let called; // 表示是否呼叫過成功或者失敗
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        try { // {then:1}
            let then = x.then;
            if (typeof then === 'function') {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}
Promise.prototype.then = function (onFulfilled, onRjected) {
    //成功和失敗預設不穿給一個函式
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            // 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
            // x可能是一個promise 也有可能是一個普通的值
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    // x可能是別人promise,寫一個方法統一處理
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })

        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}
// 捕獲錯誤的方法
Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}
// 解析全部方法
Promise.all = function (promises) {
    //promises是一個promise的陣列
    return new Promise(function (resolve, reject) {
        let arr = []; //arr是最終返回值的結果
        let i = 0; // 表示成功了多少次
        function processData(index, y) {
            arr[index] = y;
            if (++i === promises.length) {
                resolve(arr);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(function (y) {
                processData(i, y)
            }, reject)
        }
    })
};
// 只要有一個promise成功了 就算成功。如果第一個失敗了就失敗了
Promise.race = function (promises) {
    return new Promise(function (resolve, reject) {
        for (var i = 0; i < promises.length; i++) {
            promises[i].then(resolve,reject)
        }
    })
}
// 生成一個成功的promise
Promise.resolve = function(value){
    return new Promise(function(resolve,reject){
        resolve(value);
    })
}
// 生成一個失敗的promise
Promise.reject = function(reason){
    return new Promise(function(resolve,reject){
        reject(reason);
    })
}
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}

module.exports = Promise;
複製程式碼

相關文章