Promise進階——如何實現一個Promise庫

概述

從上次更新Promise/A+規範後,已經好久沒有更新博客了。以前因爲業務須要,完成了一個TypeScript語言的Promise庫。此次咱們來和你們一步一步介紹下,咱們如何實現一個符合Promise/A+規範的Promise庫。前端

若是對Promise/A+規範還不太瞭解的同窗,建議先看看上一篇博客——前端基礎知識儲備——Promise/A+規範git

實現流程

首先,咱們來看下,在我實現的這一個Promise中,代碼由下面這幾部分組成:github

  • 全局異步函數執行器
  • 常量與屬性
  • 類方法
  • 類靜態方法

經過上面這四個部分,咱們就可以獲得一個完整的Promise。這四個部分互相有關聯,接下來咱們一個一個模塊來看。typescript

全局異步函數執行器

在以前的Promiz的源碼分析的博客中我有提到過,咱們如何來實現一個異步函數執行器。經過JavaScript的執行原理咱們能夠知道,若是要實現異步執行相關函數的話,咱們能夠選擇使用宏任務和微任務,這一點在Promise/A+的規範中也有說起。所以,下面咱們提供了一個用宏任務來實現異步函數執行器的代碼供你們參考。segmentfault

let index = 0;

if (global.postMessage) {
    global.addEventListener('message', (e) => {
        if (e.source === global) {
            let id = e.data;
            if (isRunningTask) {
                nextTick(functionStorage[id]);
            } else {
                isRunningTask = true;

                try {
                    functionStorage[id]();
                } catch (e) {

                }
                isRunningTask = false;
            }

            delete functionStorage[id];
            functionStorage[id] = void 0;
        }
    });
}

function nextTick(func) {
    if (global.setImmediate) {
        global.setImmediate(func);
    } else if (global.postMessage) {
        functionStorage[++index] = func;
        global.postMessage(index, '*')
    } else {
        setTimeout(func);
    }
}

經過上面的代碼咱們能夠看到,咱們一共使用了setImmediatepostMessagesetTimeout這三個添加宏任務的方法來進行一步函數執行。數組

常量與屬性

說完了如何進行異步函數的執行,咱們來看下相關的常量與屬性。在實現Promise以前,咱們須要定義一些常量和類屬性,用於後面存儲數據。讓咱們一個一個來看下。promise

常量

首先,Promise共有5個狀態,咱們須要用常量來進行定義,具體以下:異步

enum State {
    pending = 0,
    resolving = 1,
    rejecting = 2,
    resolved = 3,
    rejected = 4
};

這五個常量分別對應Promise中的5個狀態,相信你們可以從名字中理解,咱們就很少講了。函數

屬性

在Promise中,咱們須要一些屬性來存儲數據狀態和後續的Promise引用,具體以下:工具

class Promise {
    private _value;
    private _reason;
    private _next = [];
    public state: State = 0;
    public fn;
    public er;
}

咱們對屬性進行逐一說明:

  • _value,表示在resolved狀態時,用來存儲當前的值。
  • _reason,表示在rejected狀態時,用來存儲當前的緣由。
  • _next,表示當前Promise後面跟着then函數的引用。
  • fn,表示當前Promise中的then方法的第一個回調函數。
  • er,表示當前Promise中的then方法的的第二個回調函數(即catch的第一個參數,下面看catch實現方法就能理解)。

類方法

看完了常量與類的屬性,咱們來看下類的靜態方法。

Constructor

首先,若是咱們要實現一個Promise,咱們須要一個構造函數來初始化最初的Promise。具體代碼以下:

class Promise {
    constructor(resolver?) {
        if (typeof resolver !== 'function' && resolver !== undefined) {
            throw TypeError()
        }


        if (typeof this !== 'object') {
            throw TypeError()
        }

        try {
            if (typeof resolver === 'function') {
                resolver(this.resolve.bind(this), this.reject.bind(this));
            }
        } catch (e) {
            this.reject(e);
        }
    }
}

從Promise/A+的規範來看,咱們能夠知道,若是resolver存在而且不是一個function的話,那麼咱們就應該拋出一個錯誤;不然,咱們應該將resolvereject方法傳給resolver做爲參數。

resolve && reject

那麼,resolvereject方法又是作什麼的呢?這兩個方法主要是用來讓當前的這個Promise轉換狀態的,即從pending狀態轉換爲resolving或者rejecting狀態。下面讓咱們來具體看下代碼:

class Promise {
    resolve(value) {
        if (this.state === State.pending) {
            this._value = value;
            this.state = State.resolving;

            nextTick(this._handleNextTick.bind(this));
        }

        return this;
    }

    reject(reason) {
        if (this.state === State.pending) {
            this._reason = reason;
            this.state = State.rejecting;
            this._value = void 0;

            nextTick(this._handleNextTick.bind(this));
        }

        return this;
    }
}

從上面的代碼中咱們能夠看到,當resolve或者reject方法被觸發時,咱們都改變了當前這個Proimse的狀態,而且異步調用執行了_handleNextTick方法。狀態的改變標誌着當前的Promise已經從pending狀態改變成了resolving或者rejecting狀態,而相應_value_reson也表示上一個Promise傳遞給下一個Promise的數據。

那麼,這個_handleNextTick方法又是作什麼的呢?其實,這個方法的做用很簡單,就是用來處理當前這個Promise後面跟着的then函數傳遞進來的回調函數fner

then && catch

在瞭解_handleNextTick以前,咱們們先看下then函數和catch函數的實現。

class Promise {
    public then(fn, er?) {
        let promise = new Promise();
        promise.fn = fn;
        promise.er = er;

        if (this.state === State.resolved) {
            promise.resolve(this._value);
        } else if (this.state === State.rejected) {
            promise.reject(this._reason);
        } else {
            this._next.push(promise);
        }

        return promise;
    }

    public catch(er) {
        return this.then(null, er);
    }
}

由於catch函數調用就是一個then函數的別名,咱們下面就只討論then函數。

then函數執行時,咱們會建立一個新的Promise,而後將傳入的兩個回調函數用新的Promise的屬性保存下來。而後,先判斷當前的Promise的狀態,若是已是resolved或者rejected狀態時,咱們當即調用新的Promise中resolve或者reject方法,讓下將當前Promise的_value或者_reason傳遞給下一個Promise,而且觸發下一個Promise的狀態改變。若是當前Promise的狀態仍然爲pending時,那麼就將這個新生成的Promise保存下來,等當前這個Promise的狀態改變後,再觸發新的Promise變化。最後,咱們返回了這個Promise的實例。

handleNextTick

看完了then函數,咱們就能夠來看下咱們提到過的handleNextTick函數。

class Promise {
    private _handleNextTick() {
        try {
            if (this.state === State.resolving && typeof this.fn === 'function') {
                this._value = this.fn.call(getThis(), this._value);
            } else if (this.state === State.rejecting && typeof this.er === 'function') {
                this._value = this.er.call(getThis(), this._reason);
                this.state = 1;
            }
        } catch (e) {
            this.state = State.rejecting;
            this._reason = e;
            this._value = void 0;
            this._finishThisTypeScriptPromise();
        }
        
        // if promise === x, use TypeError to reject promise
        // 若是promise和x指向同一個對象,那麼用TypeError做爲緣由拒絕promise
        if (this._value === this) {
            this.state = State.rejecting;
            this._reason = new TypeError();
            this._value = void 0;
        }
        
        this._finishThisTypeScriptPromise();
    }
}

咱們先來看一個簡單版的_handleNextTick函數,這樣可以幫助咱們快速理解Promise主流程。

異步觸發了_handleNextTick函數後,咱們會判斷當前用戶處於的狀態,若是當前Promise是resolving狀態,咱們就會調用fn函數,即咱們在then函數調用時給新的Promise設置的那個fn函數;而如過當前Promise是rejecting狀態,咱們就會調用er函數。

上面提到的getThis方法是用來獲取特定的this值,具體的規範要求咱們將在稍後再進行介紹。

經過執行這兩個同步的fner函數,咱們可以獲得當前Promise執行完傳入回調後的值。在這裏須要說明的是:咱們在執行fn或者er函數以前,咱們在_value_reason中存放的值,是上一個Promise傳遞下來的值。只有當執行完了fn或者er函數後,_value_reason中存放的值纔是咱們須要傳遞給下一個Promise的值。

你們到這裏可能會奇怪,咱們的this指向沒有發生變化,可是爲何咱們的this指向的是那個新的Promise,而不是原來的那個Promise呢?

咱們能夠從另一個角度來看待這個問題:咱們當前的這個Promise是否是由上一個Promise所產生的呢?若是是這種狀況的話,咱們就能夠理解,在上一個Promise產生當前Promise的時候,就設置了fner兩個函數。

你們可能又會問,那麼咱們第一個Promise的fner這兩個參數是怎麼來的呢?

那麼咱們就須要仔細看下上面這個邏輯了。下面咱們只討論第一個Promise處於pending的狀況,其他的狀況與這種情形基本相同。第一個Promise由於沒有上一個Promise去設置fner兩個參數,所以這兩個參數的值就是undefined。因此在上面的邏輯中,咱們已經排除了這種狀況,直接進入了_finishThisTypeScriptPromise函數中。

咱們在這裏須要特別說明下的是,有些人會認爲咱們在調用then函數傳入的兩個回調函數fner時,當前Promise就結束了,其實並非這樣,咱們是獲得了fn或者er兩個函數的返回值,再將值傳遞給下一個Promise時,上一個Promise纔會結束。關於這個邏輯咱們能夠看下_finishThisTypeScriptPromise函數。

finishThisTypeScriptPromise

_finishThisTypeScriptPromise函數的代碼以下:

class Promise {
    private _finishThisTypeScriptPromise() {
        if (this.state === State.resolving) {
            this.state = State.resolved;

            this._next.map((nextTypeScriptPromise) => {
                nextTypeScriptPromise.resolve(this._value);
            });
        } else {
            this.state = State.rejected;

            this._next.map((nextTypeScriptPromise) => {
                nextTypeScriptPromise.reject(this._reason);
            });
        }
    }
}

_finishThisTypeScriptPromise函數中咱們能夠看到,咱們在獲得了須要傳遞給下一個Promise的_value或者_reason後,利用map方法逐個調用咱們保存的新生成的Promise實例,調用它的resolve方法,所以咱們又觸發了這個Promise的狀態從pending轉變爲resolving或者rejecting

到這裏咱們就已經徹底瞭解了一個Promise從最開始的建立,到最後結束的整個生命週期。下面咱們來看下在Promise/A+規範中提到的一些分支邏輯的處理狀況。

上一個Promise傳遞的value是Thenable實例

首先,讓咱們來了解下什麼是Thenable實例。Thenable實例指的是屬性中有then函數的對象。Promise就是的一種特殊的Thenable對象。

下面,爲了方便講解,咱們將用Promise來代替Thenable進行講解,其餘的Thenable類你們能夠參考相似思路進行分析。

若是咱們在傳遞給咱們的_value中是一個Promise實例,那麼咱們必須在等待傳入的Promise狀態轉換到resolved以後,當前的Promise纔可以繼續往下執行,即咱們從傳入的Promise中獲得了一個非Thenable返回值時,咱們才能用這個值來調用屬性中的fn或者er方法。

那麼,咱們要怎麼樣才能獲取到傳入的這個Promise的返回值呢?在Promise中其實用到了一個很是巧妙的方法:由於傳入的Promise中有一個then函數(Thenable定義),所以咱們就調用then函數,在第一個回調函數fn中傳入獲取_value,觸發當前的Promise繼續執行。若是是觸發了第二個回調函數er,那麼就用在er中獲得的_reason來拒絕掉當前的Promise。具體判斷邏輯以下:

class Promise {
    private _handleNextTick() {
        let ref;
        let count = 0;

        try {
            // 判斷傳入的this._value是否爲一個thanable
            // check if this._value a thenable
            ref = this._value && this._value.then;
        } catch (e) {
            this.state = State.rejecting;
            this._reason = e;
            this._value = void 0;

            return this._handleNextTick();
        }

        if (this.state !== State.rejecting && (typeof this._value === 'object' || typeof this._value === 'function') && typeof ref === 'function') {
            // add a then function to get the status of the promise
            // 在原有TypeScriptPromise後增長一個then函數用來判斷原有promise的狀態

            try {
                ref.call(this._value, (value) => {
                    if (count++) {
                        return;
                    }

                    this._value = value;
                    this.state = State.resolving;
                    this._handleNextTick();
                }, (reason) => {
                    if (count++) {
                        return;
                    }

                    this._reason = reason;
                    this.state = State.rejecting;
                    this._value = void 0;
                    this._handleNextTick();
                });
            } catch (e) {
                this.state = State.rejecting;
                this._reason = e;
                this._value = void 0;
                this._handleNextTick();
            }
        } else {
            try {
                if (this.state === State.resolving && typeof this.fn === 'function') {
                    this._value = this.fn.call(getThis(), this._value);
                } else if (this.state === State.rejecting && typeof this.er === 'function') {
                    this._value = this.er.call(getThis(), this._reason);
                    this.state = 1;
                }
            } catch (e) {
                this.state = State.rejecting;
                this._reason = e;
                this._value = void 0;
                this._finishThisTypeScriptPromise();
            }

            this._finishThisTypeScriptPromise();
        }
    }
}

promise === value

在Promise/A+規範中,若是返回的_value值等於用戶自身時,須要用TypeError錯誤拒絕掉當前的Promise。所以咱們須要在_handleNextTick中加入如下判斷代碼:

class Promise {
        private _handleNextTick() {
        let ref;
        let count = 0;

        try {
            // 判斷傳入的this._value是否爲一個thanable
            // check if this._value a thenable
            ref = this._value && this._value.then;
        } catch (e) {
            this.state = State.rejecting;
            this._reason = e;
            this._value = void 0;

            return this._handleNextTick();
        }

        if (this.state !== State.rejecting && (typeof this._value === 'object' || typeof this._value === 'function') && typeof ref === 'function') {
            // add a then function to get the status of the promise
            // 在原有TypeScriptPromise後增長一個then函數用來判斷原有promise的狀態
            
            ...

        } else {
            try {
                if (this.state === State.resolving && typeof this.fn === 'function') {
                    this._value = this.fn.call(getThis(), this._value);
                } else if (this.state === State.rejecting && typeof this.er === 'function') {
                    this._value = this.er.call(getThis(), this._reason);
                    this.state = 1;
                }
            } catch (e) {
                this.state = State.rejecting;
                this._reason = e;
                this._value = void 0;
                this._finishThisTypeScriptPromise();
            }

            // if promise === x, use TypeError to reject promise
            // 若是promise和x指向同一個對象,那麼用TypeError做爲緣由拒絕promise
            if (this._value === this) {
                this.state = State.rejecting;
                this._reason = new TypeError();
                this._value = void 0;
            }

            this._finishThisTypeScriptPromise();
        }
    }
}

getThis

在Promise/A+規範中規定:咱們在調用fner兩個回調函數時,this的指向有限制。在嚴格模式下,this的值應該爲undefined;在寬鬆模式下時,this的值應該爲global

所以,咱們還須要提供一個getThis函數用於處理上述狀況。具體代碼以下:

class Promise {
    ...
}

function getThis() {
    return this;
}

類靜態方法

咱們經過上面說到的類方法和一些特定分支的邏輯處理,咱們就已經實現了一個符合基本功能的Promise類。那麼,下面咱們來看下ES6中提供的一些標準API咱們如何來進行實現。具體API以下:

  • resolve
  • reject
  • all
  • race

下面咱們就一個一個方法來看下。

resolve && reject

首先咱們來看下最簡單的resolvereject方法。

class Promise {
    public static resolve(value?) {
        if (TypeScriptPromise._d !== 1) {
            throw TypeError();
        }

        if (value instanceof TypeScriptPromise) {
            return value;
        }

        return new TypeScriptPromise((resolve) => {
            resolve(value);
        });
    }

    public static reject(value?) {
        if (TypeScriptPromise._d !== 1) {
            throw TypeError();
        }

        return new TypeScriptPromise((resolve, reject) => {
            reject(value);
        });
    }
}

經過上面代碼咱們能夠看到,resolvereject方法基本上就是直接使用了內部的constructor方法進行Promise構建。

all

class Promise {
    public static all(arr) {
        if (TypeScriptPromise._d !== 1) {
            throw TypeError();
        }

        if (!(arr instanceof Array)) {
            return TypeScriptPromise.reject(new TypeError());
        }

        let promise = new TypeScriptPromise();

        function done() {
            // 統計還有多少未完成的TypeScriptPromise
            // count the unresolved promise
            let unresolvedNumber = arr.filter((element) => {
                return element && element.then;
            }).length;

            if (!unresolvedNumber) {
                promise.resolve(arr);
            }

            arr.map((element, index) => {
                if (element && element.then) {
                    element.then((value) => {
                        arr[index] = value;
                        done();
                        return value;
                    });
                }
            });
        }

        done();

        return promise;
    }
}

下面咱們根據上面的代碼來簡單說下all函數的基本思路。

首先咱們須要先建立一個新的Promise用於返回,保證後面用戶調用then函數進行後續邏輯處理時能夠設置新Promise的fner這兩個回調函數。

而後,咱們怎麼獲取上面Promise數組中每個Promise的值呢?方法很簡單,咱們在前面就已經介紹過:咱們調用了每個Promise的then函數用來獲取當前這個Promise的值。而且,在每一個Promise完成時,咱們都檢查下是否全部的Promise都已經完成,若是已經完成,則觸發新Promise的狀態從pending轉換爲resolving或者rejecting

race

class Promise {
    public static race(arr) {
        if (TypeScriptPromise._d !== 1) {
            throw TypeError();
        }

        if (!(arr instanceof Array)) {
            return TypeScriptPromise.reject(new TypeError());
        }

        let promise = new TypeScriptPromise();

        function done(value?) {
            if (value) {
                promise.resolve(value);
            }

            let unresolvedNumber = arr.filter((element) => {
                return element && element.then;
            }).length;

            if (!unresolvedNumber) {
                promise.resolve(arr);
            }

            arr.map((element, index) => {
                if (element && element.then) {
                    element.then((value) => {
                        arr[index] = value;
                        done(value);
                        return value;
                    });
                }
            });
        }

        done();

        return promise;
    }
}

race的思路與all基本一致。只是咱們在處理函數上不一樣。當咱們只要檢測到數組中的Promise有一個已經轉換到了resolve或者rejected狀態(經過沒有then函數來進行判斷)時,就會當即出發新建立的Promise示例的狀態從pending轉換爲resolving或者rejecting

總結

咱們對Promise的異步函數執行器、常量與屬性、類方法、類靜態方法進行了逐一介紹,讓你們對整個Promise的構造和聲明週期有了一個深度的理解和認知。在整個開發中須要注意到的一些關鍵點和細節,我在上面也一一說明了。你們只須要按照這個思路,對照Promise/A+規範就可以完成一個符合規範的Promise庫。

最後,給你們提供一個Promise/A+測試工具,你們實現了本身的Promise後,可使用這個工具來測試是否徹底符合整個Promise/A+規範。固然,你們若是想使用個人現成代碼,也歡迎你們使用個人代碼Github/typescript-proimse

相關文章
相關標籤/搜索