前端面試之手寫Promise

前言

最近老是聽到前端面試要求手寫promise,promise做爲解決前端回調地獄的方案被普遍運用在前端異步調用上,nodejs更是出了一版返回promise IO接口,因此不管從面試仍是實際工做中熟悉promise都是一個合格前端必備技能。javascript

分析Promise

咱們知道要實現一個功能,首先要了解需求是什麼,實現Promise也是如此。前端

那麼Promise到底提供了哪些功能?咱們先把Promise中經常使用的方法列出來java

// 使用Promise的標準開頭 new Promise(),可見Promise是一個類

class Promise {
    /**
     * 經常使用方式Promise.resolve(something).then(...)
     * 可見Promise.resolve返回一個新的Promise實例
     **/
    static resolve() {}
    
    // 同上
    static reject() {}
    
    /**
     * 構造函數接收一個函數做爲入參
     * 該函數會接收resolve,reject做爲入參,而且當即執行
     * Promise將在fn調用resolve/reject後決議
     */
    constructor(fn) {}
    
    /**
     * 根據constructor咱們能夠推斷Promise有三個狀態分別是
     * pending,fullfilled,rejected
     * 咱們須要一個內部變量來保存promise的當前狀態
     */
    _status = 'pending' // promise的初始狀態爲pending
    
    /**
     * 咱們須要一個變量保存promise的決議結果
     * 考慮 new Promise((resolve) => resolve(2));
     **/
     _result
    
    /**
     * 考慮一下resolve作了什麼事情
     * 其實resolve改變promise的狀態並將入參做爲回調函數的入參
     **/
    resolve(ret) {}
    
    // 同上
    reject(ret) {}
    
    /**
     * then稱得上是promise的核心方法,到底then作了什麼,咱們考慮一下
     * then會接收兩個函數,在promise的狀態發生改變後會調用對應的函數
     * 因此在這裏then的做用應當是個事件註冊器。
     * 須要注意的是then是能屢次調用的
     * const promise = new Promise(fn)
     * promise.then(fn1)
     * promise.then(fn2)
     * 另外then是支持鏈式調用的,如promise.then(fn3).then(fn4)
     * 因此調用then還應當返回一個promise對象
     **/
    then(fn, fn2) {}
    
    // 描述完then後,咱們發現咱們須要兩個回調隊列來保存使用then註冊的回調
    _successCallback = []
    _errorCallback = []
    
    // 咱們再定義一個內部方法來異步執行這些回調函數
    // 使用入參type區分執行successCallback仍是errorCallback
    _runCallbackAsync(type) {}
}

實現

分析完Promise的核心功能後,讓咱們依次開始實現這些接口。node

// 先不看靜態方法和實例方法,從構造器開始實現
class Promise {
    ...
    constructor(fn) {
        // 根據以前描述構造器做用主要是運行傳入的fn
        // 將fn放在try catch中運行,防止fn執行出錯
        try {
            //new Promise((resolve, reject) => {})
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch(e) {
            this.reject(e)
        }
    }
    
    // resolve調用以後改變後promise的狀態,而且異步執行callback
    resolve(result) {
        // promise的狀態不是pending,說明該promise已經調用過reject/resolve
        if(this._status !== 'pending') throw new Error('重複決議promise')
        // 保存決議結果
        this._result = result
        // 異步執行callback
        this._runCallbackAsync('success')
    }
    
    // reject也是同理
    reject(err) {
        if(this._status !== 'pending') throw new Error('重複決議promise')
        this._result = err
        // 這裏咱們執行錯誤回調
        this._runCallbackAsync('error')
    }
    
    ...
}

在寫promise.then以前咱們從新考慮下這個方法到底作了什麼?咱們逐步實現這個功能點。git

  • then接收兩個函數,咱們暫且稱它爲成功回調和錯誤回調,分別在fullFilled和rejected的時候執行
  • 當promise狀態已是決議狀態時當即執行對應的成功回調/錯誤回調
  • 返回一個新的promise,新的promise在成功回調/錯誤回調執行完成後決議,並使用回調的返回做爲決議結果
class Promise {
    ...
    // 省略先後代碼
    
    then(fn1, fn2) {
        // 支持鏈式調用,當即回覆一個新的promise對象
        return new Promise((resolve, reject) => {
            /** 而後咱們要將對應的回調函數推入事件隊列,他們將在本個promise
              * 決議後由_runCallbackAsync執行。
              * 但執行完對應事件後須要將執行結果傳到下一個promise中
              * 因此咱們須要對回調函數進行小小的處理
              */
            
            // 接收決議結果
            const successCallBack = (result) => {
                try {
                    // 將promise的決議結果傳遞給回調函數去執行,並把回調函數的結果做爲下一個promise的決議結果
                    resolve(fn1(result))
                } catch(e) {
                    // 若是執行出錯,應該將錯誤信息傳遞給promise
                    reject(e)
                }
            }
            
            const errorCallback = (e) => {
                try {
                    reject(fn2(e))
                } catch(err) {
                    reject(err)
                }
            }
            
            // 將回調函數推入事件隊列
            if (fn1 && this._status !== 'error') this._successCallback.push(fn1)
            if (fn2 && this._status !== 'success') this._errorCallback.push(fn2)
            
            // 若是promise已經決議,則當即執行相應的回調
            if(this._status === 'success') this._runCallbackAsync('success')
            if(this._status === 'error') this._runCallbackAsync('error')
        })
    }
    
    // 實現
    _runCallbackAsync(type) {
        let eventQueue;
        if(type === 'error') eventQueue = this._errorCallback;
        else eventQueue = this._successCallback;
        // 執行回調, 使用settimeout模擬異步執行
        setTimeout(() => {
            eventQueue.forEach(callback => callback(this._result));
            // 清空事件隊列,兩個事件隊列都須要刪除,由於promise決議後狀態不可變動,決議執行完應當清空全部隊列,以解除引用關係
            this._errorCallback.length = 0;
            this._successCallback.length = 0;
        }, 0)
    }
    ...
}

到此Promise的核心方法已經實現完畢,剩下的方法咱們很容易使用現有方法進行實現,例如:github

class Promise {
    ···
    catch(fn) {
        return this.then(undefined, fn);
    }
    
    static resolve(any) {
        // 使用鴨子類型去判斷any的類型
        if (
            typeof any.then === 'function'
            typeof any.catch === 'function'
            ...
        ) return any;
        // 若是不是一個promise則返回一個新的promise
        return new Promise((resolve) => {
            resolve(any)
        })
    }
    ···
}

總結

因此實現promise關鍵點在於,理解promise只決議一次,有三個狀態(pending,fullFilled,rejected),then/catch支持鏈式調用(返回一個新的promise),而且理解then/catch本質是個事件註冊器,相似於then = subscribe('resolve', callback),理解這些本身手動實現一個promise仍是不難的。面試

最後的最後:剛開始寫技術文章,若是有錯漏但願可以不吝指出,若是以爲寫的還不錯點個贊是對我最大的支持,謝謝。promise

最後附上源碼連接https://github.com/MinuteWong...異步

相關文章
相關標籤/搜索