Promise原理解析

做者: HerryLohtml

本文永久有效連接: https://github.com/AttemptWeb......git

Promises對象被用於表示一個異步操做的最終完成 (或失敗), 及其結果值。主要是爲了解決異步操做的問題。es6

#Promise對象的狀態

一個 Promise對象有如下三種狀態:github

pending: 初始狀態,既不是成功,也不是失敗狀態。
fulfilled(resolved): 意味着操做成功完成。
rejected: 意味着操做失敗。
複製代碼

Promise對象內部運行的一個變化, 變化以下:npm

1. 當new Promise()被實例化後,即表示Promise 進入pending初始化狀態,準備就緒,等待運行。
2. 一旦Promise實例運行成功或者失敗以後,實例狀態就會變爲fulfilled 或者 rejected,此時狀態就沒法變動。
複製代碼

#Promise函數使用

任何系統或函數均可以簡化爲輸入輸出系統,數據輸入 ——> 黑箱 ——> 輸出,以下圖:c#

咱們能夠拿上圖來類比Promise函數,代碼以下:數組

// 實例化 Promise
new Promise((resolve, reject)=> {
    // 輸入
    AjaxRequest.post({
        url: 'url',
        data: {},
        sueccess: ()=> {
            // resolve
            resolve(res)
        },
        fail: (err)=> {
            // reject
            reject(err)
        }
    })
}).then((res)=> {
    // res 輸出
    // ...操做
}).catch((err)=> {
    // err 輸出
    // ...操做
})
複製代碼

在上面的代碼中,Promise函數參數能夠做爲輸入信息,然後通過Promise的內部處理(黑箱),在then函數或者catch函數參數中輸出信息,這是一個完整的系統(別被它分散了注意力,這個解釋的目的:讓你更加關注Promise函數內部實現)。下面咱們將解析Promise中黑箱操做。promise

#pending狀態下會運行的函數

Promise函數實例化,會先進入到pending狀態,在這個狀態下,它會運行以下函數:bash

  1. 實例化Promise構造函數微信

  2. then方法註冊回調函數

  3. catch方法註冊回調函數

  4. 調用doResolve函數執行fn

#實例化Promise構造函數

你能夠直接查看源碼:Promise函數:54行

,對照閱讀,同時,在下面的代碼中我會作沒必要要的省略。

// 首先運行,Promise構造函數
function Promise(fn) {
    // ...省略檢驗

    // _deferreds的類型,1是 single,2是 array
    this._deferredState = 0;
    // 0 - pending
    // 1 - fulfilled(resolved)
    // 2 - rejected
    // 3 - 另外一個Promise的狀態
    this._state = 0;
    // promise 執行結果
    this._value = null;
    // then註冊回調數組
    this._deferreds = null;
    // fn等於noop 即return
    if (fn === noop) return;
    // 接受Promise回調函數 和 this 做爲參數
    doResolve(fn, this);
}
複製代碼

Promise構造函數,會初始化屬性,其中參數fn就是咱們傳入的函數。其中doResolve函數接受Promise函數參數this做爲參數,this指向它本身,負責執行fn函數。等下面的then函數和catch函數的回調函數註冊完以後,doResolve函數將當即執行。

#then方法註冊回調函數

能夠查看代碼,查看源碼:then函數:72行

then方法的回調函數會被存儲在this._deferreds仔細閱讀代碼中的備註

Promise.prototype.then = function(onFulfilled, onRejected) {
    if (this.constructor !== Promise) {
        // safeThen函數也是經過調用handle函數,return 新的Promise對象
        return safeThen(this, onFulfilled, onRejected);
    }
    // 生成新的Promise對象
    var res = new Promise(noop);
    handle(this, new Handler(onFulfilled, onRejected, res));
    return res;
};

// Handler構造函數
// 它的做用是掛載 then中的回調函數 和 一個空的Promise對象
function Handler(onFulfilled, onRejected, promise){
    // then中的Fulfilled回調函數
    this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // then中的Rejected回調函數
    this.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 保存新的Promise
    this.promise = promise;
}
複製代碼
// 保存then註冊回調函數,更新回調函數狀態
function handle(self, deferred) {
    // 。。。省略

    // pedding 狀態 
    if (self._state === 0) {
        // deferred == new Handler(onFulfilled, onRejected, res)
        if (self._deferredState === 0) {
            self._deferredState = 1;
            // 存儲then回調deferred對象
            self._deferreds = deferred;
            return;
        }
        if (self._deferredState === 1) {
            self._deferredState = 2;
            // 存儲then回調deferred對象
            self._deferreds = [self._deferreds, deferred];
            return;
        }
        // 存儲then回調函數對象
        self._deferreds.push(deferred);
        return;
    }

    // 只有當進入到非pedding狀態,handleResolved纔會運行
    handleResolved(self, deferred);
}
複製代碼

Handler函數生成一個deffer對象,用於保存then函數中的onFulfilled和onRejected回調,以及返回的新的promise實例

then方法中的核心函數就是handle函數,它負責接收thisnew Handler對象。若在pedding狀態下,handle函數只負責註冊回調函數,更新回調函數狀態。在非pedding狀態下,則會執行handleResolved函數。

#catch方法註冊回調函數

查看源碼:catch函數:105行

Promise.prototype['catch'] = function (onRejected) {
  return this.then(null, onRejected);
};
複製代碼

catch方法的回調函數實際是經過then方法來完成保存的。

#調用doResolve函數執行fn

負責運行Promise實例對象中的回調函數參數fn。

// 調用doResolve函數
function doResolve(fn, promise) {
    var done = false;
    
    // tryCallTwo函數執行 相似於
    // (resolve, reject) => {if(err){reject(err);return};resolve(res)}執行;
    var res = tryCallTwo(fn, function (value) {
        if (done) return;
        done = true;
        resolve(promise, value);
    }, function (reason) {
        if (done) return;
        done = true;
        reject(promise, reason);
    });

    // fn函數調用失敗,手動運行reject函數
    if (!done && res === IS_ERROR) {
        done = true;
        reject(promise, LAST_ERROR);
    }
}
複製代碼

doResolve是同步直接調用傳入的函數。其中tryCallTwo函數做用是調用函數fn,它接受三個參數。先執行fn函數,根據結果,再執行resolve函數或reject函數。在resolve函數或reject函數被調用以前,Promise對象的狀態依然是pending

pending狀態下函數調用基本流程以下:

#進入resolve或reject狀態時會運行的函數

當初始化完以後,fn函數執行完成,接下來就會運行resolve函數或者reject函數。

  1. 調用resolve函數

  2. 調用finale函數

  3. 調用handleResolved函數

#調用resolve函數

若Promise對象的fn函數執行正常,以後就會調用resolve函數。能夠查看源碼:resolve函數:131行

function resolve(self, newValue) {
    // 。。。省略
    
    // newValue存在 & (newValue是一個對象 || newValue是一個函數)
    if (
        newValue &&
        (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
        // 獲取then函數
        var then = getThen(newValue);
        // 。。。省略

        if (
            then === self.then &&
            newValue instanceof Promise
        ) {
            // 若是newValue 是一個Promise對象,那麼調用finale函數
            self._state = 3;
            self._value = newValue;
            finale(self);
            return;
        } else if (typeof then === 'function') {
            // 若是newValue 是一個函數,就繼續調用doResolve函數
            doResolve(then.bind(newValue), self);
            return;
        }
    }
    // 標記完成,進入結束流程
    self._state = 1;
    self._value = newValue;
    finale(self);
}
複製代碼

確認newValue的值,若是newValue是一個函數,就繼續循環調用doResolve函數;若是newValue 是一個Promise對象,那麼就直接調用finale函數。都不是,則直接調用finale函數。

#調用finale函數

進入結束流程,finale結束。

function finale(self) {
    // 單個回調
    if (self._deferredState === 1) {
        // 執行handle函數,實際是執行handleResolved
        handle(self, self._deferreds);
        self._deferreds = null;
    }
    // 回調數組
    if (self._deferredState === 2) {
        for (var i = 0; i < self._deferreds.length; i++) {
            // 執行handle函數,實際是執行handleResolved
            handle(self, self._deferreds[i]);
        }
        self._deferreds = null;
    }
}
複製代碼

finale函數表示進入結束流程,執行handle函數。同時在上面已經說到,在非pedding狀態下,執行handle函數,實際會是執行handleResolved函數

#調用handleResolved函數

handleResolved負責收尾工做,負責執行then或者catch方法註冊的回調函數。仔細閱讀代碼中的備註

var asap = require('asap/raw');

function handleResolved(self, deferred) {
    asap(function() {
        var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
        // 不存在 onFulfilled & onRejected
        // deferred.promise 只是一個空的Promise對象
        if (cb === null) {
            // 1 - fulfilled(resolved)
            if (self._state === 1) {
                resolve(deferred.promise, self._value);
            } else {
                reject(deferred.promise, self._value);
            }
            return;
        }
        // 執行cb回調函數
        var ret = tryCallOne(cb, self._value);
        if (ret === IS_ERROR) {
            // 錯誤,報reject
            reject(deferred.promise, LAST_ERROR);
        } else {
            resolve(deferred.promise, ret);
        }
    });
}
複製代碼

經過異步asap

調用,若不存在 onFulfilledonRejected,直接調用 resolvereject。若存在,則 tryCallOne回調的結果,直接調用 resolvereject。其中的 deferred就是上文提到的 new Handler實例對象。真正會影響最後這步流程的,實際上是 deferred.onFulfilled或者 deferred.onRejected的回調執行,執行完回調後,這個Promise的執行過程就基本完成。

reject函數在這裏我就不說了,有興趣的能夠看查看源碼:reject函數

Promise對象調用函數的基本流程圖,只是一個大體的走向,便於理解:

#參考

Promises/A+ 規範

MDN中文: Promise對象

Github: then/promise 源碼

tc39: tc39 ecma262 promise

#感謝

掘金:代碼君的自由:解讀Promise內部實現原理

簡書:烏龜怕鐵錘:Promise 源代碼解析

ps: 微信公衆號:Yopai,有興趣的能夠關注,每週不按期更新,分享能夠增長世界的快樂

相關文章
相關標籤/搜索