Promise(2):手動實現Promise

以 Promise/A+ 作爲標準,編寫一個可經過標準測試的Promise類庫。git

Promise類的結構

  1. Promise對象初始狀態爲 Pending,在被 resolve 或 reject 時,狀態變爲 Fulfilled 或 Rejected
  2. resolve接收成功的數據,reject接收失敗或錯誤的數據
  3. Promise對象必須有一個 then 方法,且只接受兩個可函數參數 onFulfilled、onRejected
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
const PENDING = 'pending';

class MPromise{
    constructor(resolver){
        if(resolver && typeof resolver !== 'function'){
            throw new Error('MPromise resolver is not function');
        }
        
        this.state = PENDING; //當前promise對象的狀態
        this.data = undefined; //當前promise對象的數據(成功或失敗)
        this.callbackQueue = []; //當前promise對象註冊的回調隊列
        
        if(resolver){
           executeResolver.call(this, resolver);
        }
    }
    then(){
        //todo
    }
}
複製代碼

因此,一個 Promise構造函數 和一個實例方法then 就是Promise的核心的了,其它的都是Promise的語法糖或者說是擴展github

executeResolver

構造器的初始化,使用 new Promise(function(resolve, reject){...})實例化 Promise 時,去改變promise的狀態,是執行 resolve() 或 reject()方法,那麼,resolver的兩個參數分別是成功的操做函數和失敗的操做函數。promise

function executeResolver(resolver){
    let called = false; //狀態變動不可逆
    let _this = this;
    function onError(reason){
        if(!called){
            return;
        }
        called = true;
        executeCallback.call(_this,'reject', reason);
    }
    function onSuccess(value){
        if(!called){
            return;
        }
        called = true;
        executeCallback.call(_this, 'resolve', value);
    }
    try{
        resolver(onSuccess, onError);
    }catch(e){
        onError(e);
    }
}
複製代碼

這裏將上面執行 resolver 的方法抽象出來,內部再將 resovle 和 reject 兩個參數包裝成 成功和失敗的回調。瀏覽器

executeCallback

由於執行 resolve() 或 reject() 內部主要做用是更改當前實例的狀態爲 rejected 或 resolved,而後執行當前實例 then() 中註冊的 成功或失敗的回調函數, 因此從過程上來看,大體是相同的,抽象出來共用安全

function executeCallback(type, x){
    const isResolve = type === 'resolve' ? true : false;
    let thenable;
    
    if(isResolve && typeof x === 'object' && typeof x.then === 'function'){
        try {
            thenable = getThen(x);
        } catch (e) {
            return executeCallback.call(this, 'reject', e);
        }
    }
    if(isResolve && thenable){
        executeResolver.call(this, thenable); //注意是this
    } else {
        this.state = isResolve ? RESOLVED : REJECTED;
        this.data = x;
        this.callbackQueue.forEach(v => v[type](x));
    }
    return this;
}
複製代碼
function getThen(obj){
    const then = obj && obj.then;
    if(obj && typeof obj === 'object' && typeof then === 'function'){
        return applyThen(){
            then.apply(obj, arguments)
        }
    }
}
複製代碼

then

標準中規定:bash

  1. then 方法必須返回一個新的 Promise實例(ES6中的標準,Promise/A+中沒有明確說明)
  2. 爲了保證 then中回調的執行順序,onFulfilled 或 onRejected 必須異步調用
class MPromise{
    ...
    then(onResolved, onRejected){
        //回調不是函數,能夠忽略
        if(this.state === RESOLVED && onResolved !== 'function' 
            || this.state === REJECTED && onRejected !== 'function'){
            return this;
        }
        let promise = new MPromise();
        if(this.state !== PENDING){
            var callback = this.state === RESOLVED ? onResolved : onRejected;
            //注意:傳入promise,
            //異步調用
            executeCallbackAsync.call(promise, callback, this.data);
        } else {
            this.callbackQueue.push(new CallbackItem(promise, onResolved, onRejected))
        }
        return promise; //必須返回promise,才能鏈式調用
    }
}
複製代碼

executeCallbackAsync

上面將異步調用callback的邏輯抽象成了一個方法executeCallbackAsync ,這個方法主要功能是安全的執行callback方法:app

  1. 若是出錯,則自動調用 reject(reason) 方法並更改狀態爲 rejected,傳遞錯誤數據給當前實例then方法中註冊的onRejected 回調
  2. 若是成功,則自動調用 resolve(value)方法並更改狀態爲resolved,傳遞數據給當前實例then方法中註冊的 onResolved 回調
function executeCallbackAsync(callback, value){
    let _this = this;
    setTimeout(() => {
        let res;
        try{
            res = callback(value);
        }catch(e){
            return executeCallback.call(_this, 'reject', e);
        }
        if(res !== _this){
            return executeCallback.call(_this, 'resolve', res);
        } else {
            return executeCallback.call(_this, 'reject', new TypeError('Cannot resolve promise with itself'));
        }
    }, 4);
}
複製代碼

注意這裏最好不要用 setTimeout ,使用 setTimeout 能夠異步執行回調,但其實並非真正的異步線程,而是利用了瀏覽器的 Event Loop 機制去觸發執行回調,而瀏覽器的事件輪循時間間隔是 4ms ,因此鏈接的調用 setTimeout 會有 4ms 的時間間隔,而在Nodejs 中的 Event Loop 時間間隔是 1ms,因此會產生必定的延遲,若是promise鏈比較長,延遲就會越明顯,這裏能夠引入NPM上的 immediate 模塊來異步無延遲的執行回調。異步

CallbackItem

上面then中對於回調的處理,使用了一個回調對象來管理註冊的回調,將回調按順序添加至 callbackQueue 隊列中,調用時,依次調用。函數

class CallbackItem {
    constructor(promise, onResolved, onRejected) {
        this.promise = promise;
        this.onResolved = typeof onResolved === 'function' ? onResolved : function (v) { 
            return v;
        };
        this.onRejected = typeof onRejected === 'function' ? onRejected : function (v) { 
            throw v; 
        };
    }
    resolve(value) {
        executeCallbackAsync.call(this.promise, this.onResolved, value);
    }
    reject(value) {
        executeCallbackAsync.call(this.promise, this.onRejected, value);
    }
}
複製代碼

例子

以上參考深刻理解Promise中的文章,實現MPromiseoop

function fn() {
    let promise1 = new MPromise((resolve, reject) => {
        resolve(1); 
    });
    new MPromise((resolve, reject) => {
        resolve(promise1); //系統執行promise1.then
    }).then(res => {
        console.log(res);
        return 222;
    }).catch(err => {
        console.log(err);
    });
}
fn(); // 1
複製代碼

參考文章

深刻理解 Promise (中)

MPromise的git地址

相關文章
相關標籤/搜索