Promise 規範解讀及實現細節 (二)

_ivenj發表於2019-02-16

開始前

Promise的實現原理已經在 Promise 規範解讀及實現細節 (一) 中說的很清楚了,這裡將詳細分析 Promises/A+規範 中的Promise解析過程,最後會實現一個 Promise 並提供用於測試的程式碼

then 方法分析

promise.then(fn1).then(fn2).then(fn3) 這裡 promise.then() 的呼叫會產生一個新的promise(不同例項)

對於 then(fn) 上一級 promise 的終值會作為 fn 的引數被傳入

對於 then(fn) 如果 then 返回一個 promise1,fn 返回一個 promise2 那麼 promise1 的狀態和值由 promise2 決定

對於 then(fn) 如果 then 返回一個 promise1,fn 返回一個 promise2 我們想依賴於promise2 的呼叫會被新增到 promise1

如何讓 promise2 決定 promise1 的結果

如果promise2thenvar promise2Then = promise2.then,我們這樣promise2Then.bind(promise1,resolve,reject)

promise 的狀態和終值都是通過 當前 promiseresolvereject 來改變的,所以只需要將 promise1 的這兩個函式通過promise2then 方法新增的 promise2 的執行佇列中就可以達到想要的效果

Promise 解析過程

對於 promise.then(fn),then 方法返回 promise1fn 方法返回 x,fn 接收到的引數為 y,這時需要對 xy 分別執行Promise解析過程

x: [[Resolve]](promise1, x)
y: [[Resolve]](promise1, y)
x 的解析過程處理的是 回掉中返回 promise 的情況
y 的解析過程處理的是 向當前 promise 傳遞處理結果的那個 promise 的終值是 promise 的情況

由此可見 promise 的解析過程是遞迴的,遞迴的終點是 x,y 不是promise,在是物件或者函式的情形下不具備 then 方法

程式碼結構

(function(window) {
    var PENDING = 0; //PENDING 狀態
    var RESOLVED = 1; //RESOLVED 狀態
    var REJECTED = 2; //REJECTED 狀態
    function IPromise(fn) {
        if (!(this instanceof IPromise)) return new IPromise(fn); //確保 通過 new IPromise() 和 IPromise() 都能正確建立物件
        var state = PENDING; //promise 狀態
        var value = null; //終值
        var callback = []; //回掉函式佇列,在一種是兩個佇列,這裡是一個,所以存放的是物件

        function reject(reason) {} //reject 方法
        function resolve(result) {} //和(一)中的對比 這裡多了 執行 Promise 解析過程 的功能
        function handle(handler) {} //新增或執行隊 callback 中的呼叫

        /**
        *@param onFulfilled 通過 then 方法新增的 onFulfilled
        *@param onRejected 通過 then 方法新增的 onRejected
        *
        *@func 包裝使用者新增的回撥 
        *因為這裡只有一個回掉佇列所以需要用 candy(糖果) 包裝成{onFulfilled:onFulfilled,onRejected:onRejected}
        *
        *@func 延遲呼叫handle
        *在 Promise 規範解讀及實現細節 (一) 中說 Promise(瀏覽器實現) 會被加入到microtask,由於瀏覽器沒有提供除Promise
        *之外microtask的介面,所以 我們要用 setTimeout 來延遲呼叫並新增到 macrotask
        *
        */
        function candy(onFulfilled, onRejected) {}

        function getThen(value) {} //判斷 value 是否有 then 方法如果有則獲取

        this.then = function(onFulfilled, onRejected) {} //暴露的 then 方法
        doResolve(fn, resolve, reject); //執行 fn
        window.IPromise = IPromise; //將 IPromise 新增到瀏覽器的 window 上
    }
    /**
    *Promise 解析過程
    */
    function doResolve(fn, resolvePromise, rejectPromise) {} //靜態私有方法,解析並執行promise(解析並執行fn和promise的處理結果)
})(window);

以上通過 js 自執行函式將變數和函式限制在了作用域中,在全域性的 window 上只暴露一個建構函式 IPromise 保證了全域性不被汙染

具體程式碼及解釋(請在瀏覽器中執行)

/**
 *@author ivenj
 *@date 2016-12-06
 *@version 1.0 
 */
(function(window) {
    var PENDING = 0;
    var RESOLVED = 1;
    var REJECTED = 2;

    function IPromise(fn) {
        if (!(this instanceof IPromise)) return new IPromise(fn);
        var state = PENDING;
        var value = null;
        var callback = [];

        function reject(reason) {
            state = REJECTED;
            value = reason;
            callback.forEach(handle);
            callback = null;
        }

        /**
        * 這裡新增的內容是 滿足Promise解析過程時 resolve和doResolve相互呼叫形成遞迴
        **/
        function resolve(result) {
            try {
                var then = getThen(result);
                if (then) {
                    doResolve(then.bind(result), resolve, reject);  //aa
                    return; //這裡如果 resule 是有 then 方法則執行 doResolve 並返回不執行後續程式碼
                }
                //只有 result 不滿足 解析過程時執行,即遞迴終點
                state = RESOLVED;
                value = result;
                callback.forEach(handle);
                callback = null;
            } catch (e) {
                reject(e);
            }
        }

        function handle(handler) {
            if (state === PENDING) {
                callback.push(handler);
            } else {
                if (state === RESOLVED && typeof handler.onFulfilled === `function`) {
                    handler.onFulfilled(value);
                }
                if (state === REJECTED && typeof handler.onRejected === `function`) {
                    handler.onRejected(value);
                }
            }
        }

        function candy(onFulfilled, onRejected) {
            setTimeout(function() {
                handle({
                    onFulfilled: onFulfilled,
                    onRejected: onRejected
                });
            }, 0);
        }

        function getThen(value) {
            var type = typeof value;
            if (value && (type === `object` || type === `function`)) {
                try{
                    var then = value.then;
                }catch(e){
                    reject(e);
                }
                if (typeof then === `function`) {
                    return then;
                }
            }
            return null;
        }

        this.then = function(onFulfilled, onRejected) {
            var self = this;
            return new IPromise(function(resolve, reject) {
                candy(function(x) {
                    if (typeof onFulfilled === `function`) {
                        try {
                            resolve(onFulfilled(x)); //cc 執行 [[Resolve]](promise, x)
                        } catch (e) {
                            reject(e);
                        }
                    } else {
                        resolve(x);
                    }
                }, function(error) {
                    if (typeof onRejected === `function`) {
                        try {
                            resolve(onRejected(error));
                        } catch (e) {
                            reject(e);
                        }
                    } else {
                        reject(error);
                    }
                });
            });
        };
        doResolve(fn, resolve, reject);
    }

    /**
    *Promise 解析過程
    */
    function doResolve(fn, resolvePromise, rejectPromise) {
        var done = false; //用於保證只呼叫一次
        try {
            fn(function(y) {
                if (done) return;
                done = true;
                resolvePromise(y); //bb 如果 resolvePromise 以值 y 為引數被呼叫,則執行 [[Resolve]](promise, y)
            }, function(reason) {
                if (done) return;
                done = true;
                rejectPromise(reason);
            });
        } catch (e) {
            if (done) return;
            done = true;
            rejectPromise(e);
        }
    }
    window.IPromise = IPromise;
})(window);

這裡是用於測試的程式碼 讀者將以上程式碼和以下程式碼貼上到瀏覽器去執行 一秒後會列印 {url: "http://ivenj_", value: 10}

function post(url, callback) {
    setTimeout(function() {
        var data = { //模擬非同步處理結果
            url:url,
            value:10
        };
        callback(data);
    }, 1000);
}

var promise = IPromise(function(resolve, reject){
    post(`http://ivenj_`, function(data){
        resolve(data);
    });
});

promise.then(function(data){
    console.log(data);
});

Promise 實現最核心的內容是程式碼中的 //aa //bb //cc 讀者需要著重體會這三處
Promise 到此已經結束

相關文章