Promise原始碼閱讀之建構函式+then過程
前言
Promise是非同步程式設計的一種方案,ES6規範中將其寫入規範標準中,統一了用法。
考慮到瀏覽器的相容性,Vue專案中使用promise,就具體閱讀promise原始碼,看看內部的具體實現。
具體分析
通過具體例項來閱讀promise原始碼的實現,例項如下:
new Promise(function (resolve, reject) {
get('http://www.google.com', function (err, res) {
if (err) reject(err);
else resolve(res);
})
}).then (function(res) {
// 相關處理
});
Promise庫的GitHub地址,實際上在原始碼中只需要關注下面幾個檔案即可(這幾個檔案實現了經常使用的API):
- core.js
- es6-extensions.js
- finally.js
core.js
核心檔案,核心功能是:
提供Promise建構函式的定義以及resolve、reject的具體處理
ES6 Promises規範中,Promise代表非同步操作,其狀態有三個:
pending:進行中
fulfilled:已成功
rejected: 已失敗
因為promise中需要關注的程式碼量不是很多,就結合具體的程式碼進行。
Promise建構函式
Promise建構函式中的處理:
function Promise(fn) {
// 防止Promise()作為函式呼叫
// 瀏覽器中作為函式此處this === window,並沒有起到要達到的效果
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
// 必須要傳遞函式
if (typeof fn !== 'function' ) {
throw new TypeError('Promise constructor\'s argument is not a function');
}
// Promise內部維護的狀態,預設為0即為pending,進行中
this._deferredState = 0;
this._state = 0;
this._value = null;
this._deferreds = null;
if (fn === noop) return;
doResolve(fn, this);
}
doResolve
從doResolve函式可知,內部實際上是呼叫tryCallTwo函式來處理,而tryCallTwo函式的處理實際上就是呼叫Promise中傳遞的fn函式,並將resolve和reject的回撥函式傳遞到fn中,原始碼如下:
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
Last_ERROR = ex;
return IS_ERROR;
}
}
這裡a就是表示處理resolve狀態的函式,相對應的b就是處理rejected狀態的函式
resolve
從上面邏輯中可知當執行resolve時,內部會呼叫已定義好的resolve函式來處相關邏輯。
這裡需要的點在getThen函式以及then結果的判斷,處理了三個情況:
- getThen執行錯誤的處理
- then是Promise物件的處理
- then是函式的處理
為什麼要這麼處理這幾種情況,就需要去看getThen具體實現的功能,通過原始碼可知:
function getThen(obj) {
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
通過上面的邏輯處理可知:
resolve這邊實際上就是處理newValue,即執行了new Promise後之後傳遞給resolve的引數問題
主要的判斷引數是否存在並且是否是object或function,如果是object或function,則會呼叫getThen獲取then進行下面的判斷:
- then是否等於IS_ERROR,即getThen是否執行出錯
- newValue是否是Promise物件
- then是否是函式
實際上上面的判斷都是處理newValue中是否存在then函式的問題。
只要newValue中不存在then函式,就會設定_state和_value,並呼叫finale函式。
reject函式
reject中處理邏輯就是設定state和value值,呼叫finale函式。
state設定為2,表示rejected狀態
finale函式
finale函式式實際上是處理fulfilled狀態和rejected,分別呼叫handle來處理相關邏輯。
當你使用new Promise實際上_deferredStat預設為0,而且在之前的處理邏輯中並沒有相關處理,因為Promise中是非同步操作實際上這邊是在then函式中處理。
then函式
Promise.prototype.then = function(onFulfilled, onRejected) {
// 處理非Promise建構函式建立的情況,例如prototype繼承未修改constructor時
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};
從上面可知鏈式呼叫的實現以及then函式內部的主要處理是handle函式。
首先看看Handler建構函式的作用,實際上是then函式內部會構建promise物件的連結串列結構,Handler的屬性:
function Handler(onFulfilled, onRejected, promise){
// then函式的resolve函式
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
// then函式的reject函式
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
// 下一個promise物件,實際上就是then函式返回的
this.promise = promise;
}
而handle中的處理邏輯主要點如下:
判斷上一個Promise中state,根據狀態設定_deferredState和_deferreds的值
根據上一個Promise中state不為0,就會執行handleResolved
實際上handle就是判斷上一個Promise中的非同步操作是否執行了,沒有執行就等待。
實際上上一個promise執行了,就會改變_state的值,而then函式根據_state的值來做相應的判斷
而在handleResolved函式中實際上會呼叫resolve或reject重複new Promise中一部分動作,這裡就需要通過例項去整體梳理這樣好理解些。
根據例項梳理整個過程如下:
- new Promise(fn)構建promise例項物件,會立即執行fn函式
- fn函式式get請求,這是一個非同步操作,而這個非同步操作的處理邏輯中會主動觸發resolve或reject,所以new Promise建立的promise會等待主動觸發resolve或reject
- Promise等待,但是這邊的同步程式碼then函式就執行了,呼叫Promise.prototype.then暴露的API
- then函式中就構建另一個Promise,建立Hander物件形成鏈式,並執行handle函式
- handle函式執行就要判斷上一個Promise例項的_state(例項這裡是new Promise建立的),因為上一個Promise執行非同步操作的,_state為0
- _state為0,就設定了_deferredState為1,然後等待上一個Promise執行完非同步操作
當例項中new Promise中的get請求成功後,就會觸發resolve,此時:
- 會呼叫resolve函式,resolve函式中則會判斷傳遞進來的資料是否是物件或函式(即處理存在then函式的情況)
- 如果不存在then函式的情況下,就設定_state以及_value值,並呼叫finale函式
- finale函式中就判斷_deferredState值,在上一個Promise等待非同步操作中then函式就設定了該值為1,則會呼叫handle函式
- 實際上呼叫handle就是去執行handleResolved,執行then函式傳遞進來的resolve函式,即使用asap來執行resolve函式(如果此時resolve是一個非同步操作,實際上就是重複new Promise中主動觸發resolve的過程),注意此時是另一個Promise物件了
- 此時resolve的處理就是將上一個Promise傳遞進來的value值設定為下一個Promise的value值,並將當前的Promise的state狀態置為1
- 此時會執行finale,實際上這個函式在此時沒有執行任何邏輯,因為此時_state為1,即使再接then函式,也不會設定_deferredState了,而是直接執行handleResolved,因為此步實際上是被asap主動執行了
如此反覆上面的過程。
以當時例項promise整個邏輯處理如下: