解析 Promise 原理,實現一個Promise

解析 Promise 原理,實現一個Promise

概述

這篇文章旨在解析 Promise的異步實現原理,而且以 ES6中的 Promise 爲藍本實現一個簡單的 Promise。git

經過本身動手實現一個 Promise 對象,能夠熟悉不少可能不知道的 Promise 細節,同時也能對異步的理解更提高一步。es6

本文假設讀者對 Promise 規範有必定理解,而且熟悉 ES6 中的 Promise 基本操做。github

Promise 核心

  • Promise 歸納來講是對異步的執行結果的描述對象。(這句話的理解很重要)
  • Promise 規範中規定了,promise 的狀態只有3種:promise

    1. pending
    2. fulfilled
    3. rejected

顧名思義,對上面3個狀態的解釋就再也不贅述,Promise 的狀態一旦改變則不會再改變瀏覽器

  • Promise 規範中還規定了 Promise 中必須有 then 方法,這個方法也是實現異步的鏈式操做的基本。

具體的規範能夠參見:https://promisesaplus.comdom

ES6 Promise細節

  1. Promise 構造器中必須傳入函數,不然會拋出錯誤。(沒有執行器還怎麼作異步操做。。。)
  2. Promise.prototype上的 catch(onrejected) 方法是 then(null,onrejected) 的別名,而且會處理鏈以前的任何的reject。
  3. Promise.prototype 上的 then和 catch 方法總會返回一個全新的 Promise 對象
  4. 若是傳入構造器的函數中拋出了錯誤,該 promise 對象的[[PromiseStatus]]會賦值爲 rejected,而且[[PromiseValue]]賦值爲 Error 對象。
  5. then 中的回調若是拋出錯誤,返回的 promise 對象的[[PromiseStatus]]會賦值爲 rejected,而且[[PromiseValue]]賦值爲 Error 對象。
  6. then 中的回調返回值會影響 then 返回的 promise 對象。(下文會具體分析)

這部份內容參考: http://es6.ruanyifeng.com/#do...異步

動手實現

作了上面的鋪墊,實現一個 Promise 的思路就清晰不少了,本文使用 ES6 來進行實現,暫且把這個類取名爲 GPromise吧(不覆蓋原生的,便於和原生進行對比測試)。下文中 GPromise 代指將要實現的類,Promise 代指 ES6中的 Promise 類。async

內部屬性

在瀏覽器中打印出一個 Promise 實例會發現其中會包括兩用"[[ ]]"包裹起來的屬性,這是系統內部屬性,只有JS 引擎可以訪問。函數

[[PromiseStatus]]
[[PromiseValue]]

以上兩個屬性分別是 Promise 對象的狀態和最終值。測試

咱們本身不能實現內部屬性,JS中私有屬性特性(#修飾符如今仍是提案)暫時也沒有支持,因此暫且用"_"前綴規定私有屬性,這樣就模擬了Promise 中的兩個內部屬性。

class GPromise {
        constructor(executor) {
            this._promiseStatus = GPromise.PENDING;
            this._promiseValue;
            this.execute(executor);
        }
        
        execute(executor){
            //...
        }
        
        then(onfulfilled, onrejected){
            //...
        }
    }

    GPromise.PENDING = 'pedding';
    GPromise.FULFILLED = 'resolved';
    GPromise.REJECTED = 'rejected';

執行器

  1. 傳入構造器的executor爲函數,而且在構造時就會執行。
  2. 咱們給 executor 中傳入 resolve 和 reject 參數,這兩個參數都是函數,用於改變改變 _promiseStatus和 _promiseValue 的值。
  3. 而且內部作了捕獲異常的操做,一旦傳入的executor 函數執行拋出錯誤,GPromise 實例會變成 rejected狀態,即 _promiseStatus賦值爲'rejected',而且 _promiseValue賦值爲Error對象。
execute(executor) {
            if (typeof executor != 'function') {
                throw new Error(` GPromise resolver ${executor} is not a function`);
            }
            //捕獲錯誤
            try {
                executor(data => {
                    this.promiseStatus = GPromise.FULFILLED;
                    this.promiseValue = data;
                }, data => {
                    this.promiseStatus = GPromise.REJECTED;
                    this.promiseValue = data; 
                });
            } catch (e) {
                this.promiseStatus = GPromise.REJECTED;
                this.promiseValue = e;
            }
        }

注:Promise 對象在executor 發生錯誤或者reject 時,若是沒有then
或者 catch 來處理,會把錯誤拋出到外部,也就是會報錯。GPromise 實現的是沒有向外部拋出錯誤,只能由then方法處理。

then方法

異步實現

then 方法內部邏輯稍微複雜點,而且有一點必定必定必定要注意到: then 方法中的回調是異步執行的,思考下下段代碼:

console.log(1);
new Promise((resolve,reject)=>{
    console.log(2);
    resolve();
})
.then(()=>console.log(3));
console.log(4);

執行結果是什麼呢?答案實際上是:1 2 4 3。傳入Promise 中的執行函數是當即執行完的啊,爲何不是當即執行 then 中的回調呢?由於then 中的回調是異步執行,表示該回調是插入事件隊列末尾,在當前的同步任務結束以後,下次事件循環開始時執行隊列中的任務。

then 方法中的難點就是處理異步,其中一個方案是經過 setInterval來監聽GPromise 對象的狀態改變,一旦改變則執行相應then 中相應的回調函數(onfulfilled和onrejected),這樣回調函數就可以插入事件隊列末尾,異步執行,實驗證實可行,這種方案是最直觀也最容易理解的。

then 返回值

then 方法的返回值是一個新的 GPromise 對象,而且這個對象的狀態和 then 中的回調返回值相關,回調指代傳入的 onfulfilled 和 rejected。

  1. 若是 then 中的回調拋出了錯誤,返回的 GPromise 的 _promiseStatus 賦值爲'rejected', _promiseValue賦值爲拋出的錯誤對象。
  2. 若是回調返回了一個非 GPromise 對象, then返回的 GPromise 的 _promiseStatus 賦值爲'resolved', _promiseValue賦值爲回調的返回值。
  3. 若是回調返回了一個 GPromise 對象,then返回的GPromise對象 的_promiseStatus和 _promiseValue 和其保持同步。也就是 then 返回的GPromise記錄了回調返回的狀態和值,不是直接返回回調的返回值。
代碼

then 方法中的重點邏輯如上,其餘參見代碼便可:

then(onfulfilled, onrejected) {
            let _ref = null,
                timer = null,
                result = new GPromise(() => {});

            //由於 promise 的 executor 是異步操做,須要監聽 promise 對象狀態變化,而且不能阻塞線程
            timer = setInterval(() => {
                if ((typeof onfulfilled == 'function' && this._promiseStatus == GPromise.FULFILLED) ||
                    (typeof onrejected == 'function' && this._promiseStatus == GPromise.REJECTED)) {
                    //狀態發生變化,取消監聽
                    clearInterval(timer);
                    //捕獲傳入 then 中的回調的錯誤,交給 then 返回的 promise 處理
                    try {
                        if (this._promiseStatus == GPromise.FULFILLED) {
                            _ref = onfulfilled(this._promiseValue);
                        } else {
                            _ref = onrejected(this._promiseValue);
                        }

                        //根據回調的返回值來決定 then 返回的 GPromise 實例的狀態
                        if (_ref instanceof GPromise) {
                            //若是回調函數中返回的是 GPromise 實例,那麼須要監聽其狀態變化,返回新實例的狀態是根據其變化相應的
                            timer = setInterval(()=>{
                                if (_ref._promiseStatus == GPromise.FULFILLED ||
                                    _ref._promiseStatus == GPromise.REJECTED) {
                                    clearInterval(timer);
                                    result._promiseValue = _ref._promiseValue;
                                    result._promiseStatus = _ref._promiseStatus;
                                }
                            },0);
                            
                        } else {
                            //若是返回的是非 GPromise 實例
                            result._promiseValue = _ref;
                            result._promiseStatus = GPromise.FULFILLED;
                        }
                    } catch (e) {
                        //回調中拋出錯誤的狀況
                        result._promiseStatus = GPromise.REJECTED;
                        result._promiseValue = e;
                    }
                }
            }, 0);
            //promise 之因此可以鏈式操做,由於返回了GPromise對象
            return result;
        }

測試用例

是騾子是馬,拉出來溜溜。。

測試環境是macOS Sierra 10.12.6,Chrome 60.0.3112.113。

通過如下測試, 證實了GPromise 的基本的異步流程管理和原生 Promise 沒有差異。如下測試用例參考了 MDN 中的[Promise
API](https://developer.mozilla.org... 中的 Advanced Example。

var promiseCount = 0;

    function test(isPromise) {
        let thisPromiseCount = ++promiseCount,
            executor = (resolve, reject) => {
                console.log(thisPromiseCount + ') Promise started (Async code started)');
                window.setTimeout(
                    function () {
                        resolve(thisPromiseCount);
                    }, Math.random() * 2000 + 1000);
            };

        console.log(thisPromiseCount + ') Started (Sync code started)');

        let p1 = isPromise ? new Promise(executor) : new GPromise(executor);

        p1.then(
            function (val) {
                console.log(val + ') Promise fulfilled (Async code terminated)');
            },
            function (reason) {
                console.log('Handle rejected promise (' + reason + ') here.');
            });

        console.log(thisPromiseCount + ') Promise made (Sync code terminated)');
    }

    test();
    test(true);
    test();

那麼再來測試下鏈式操做(沒有鏈式操做的 Promise 我要你有何用?),測試結果和 Promise 表現一致。

function async1() {
        return new GPromise(
            (resolve, reject) => {
                console.log('async1 start');
                setTimeout(() => {
                    resolve('async1 finished')
                }, 1000);
            }
        );
    }

    function async2() {
        return new GPromise(
            (resolve, reject) => {
                console.log('async2 start');
                setTimeout(() => {
                    resolve('async2 finished')
                }, 1000);
            }
        );
    }

    function async3() {
        return new GPromise(
            (resolve, reject) => {
                console.log('async3 start');
                setTimeout(() => {
                    resolve('async3 finished');
                }, 1000);
            }
        );
    }

    async1()
        .then(
            data => {
                console.log(data);
                return async2();
            })
        .then(
            data => {
                console.log(data);
                return async3();
            }
        )
        .then(
            data => {
                console.log(data);
            }
        );

總結

到此爲止,一個高仿的 Promise 已經實現完成了,它很簡單,由於只有一個 then 方法,異步的狀態管理由內部完成。

這裏並無實現 catch方法,由於上文也提到了,catch方法就至關於 then(null,onrejected) 。並且 Promise 類上的 race,all,resolve,reject也沒有實現,本文旨在理清 Promise 核心原理,篇幅受限(其實就是我懶),其餘輔助類的方法等以後有時間再實現。

本文提供的只是一個思路,但願能幫助到你,歡迎你們批評指教。

代碼地址:Github

相關文章
相關標籤/搜索