Promise核心原理解析


作者: HerryLo

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

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

#Promise對象的狀態

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

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

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

1. 當new Promise()被實例化后,即表示Promise 進入pending初始化狀態,准備就緒,等待運行。
2. 一旦Promise實例運行成功或者失敗之后,實例狀態就會變為fulfilled 或者 rejected,此時狀態就無法變更。

#Promise函數使用

任何系統或函數都可以簡化為輸入輸出系統,數據輸入 ——> 黑箱 ——> 輸出,如下圖:

我們可以拿上圖來類比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中黑箱操作。

#pending狀態下會運行的函數

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

  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,有興趣的可以關注,每周不定期更新,分享可以增加世界的快樂


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM