Promise源碼解析
紙上得來終覺淺,絕知此事要躬行。之前只是很淺顯的知道Promise的用法,也大概猜測到它的內部是如何實現的。但是總是有一種不深究一下就不踏實的感覺。於是從npm上獲得早期的Promise源代碼,拿過來讀一讀,做一做筆記。
Promise的源碼寫的非常的巧妙,憑空閱讀會陷入入其中無法自拔。
簡單的Promise用法
一個簡單的Promise用法如下:
const promiseObj = new Promise(function (resolve, reject) {
// 代碼執行
resolve('value');
//or
reject('error');
});
promiseObj.then(function (value) {
// do sth...
}, function (error) {
// deal excption...
});
promiseObj.catch(function (error) {
// catch excption...
});
我們就從這個簡單的例子開始分析Promise的執行是怎麼樣的。
Promise的源代碼位於:https://github.com/stefanpenner/es6-promise.git ,這裏採用的版本爲0.1.0版本。
我們從Promise的構造函數開始說起:
function Promise(resolver) {
... // 省略的部分爲必要的校驗
this._subscribers = [];
invokeResolver(resolver, this);
}
_subscribers對象用於存儲這個Promise實例所對應的觀察者。緊接着執行invokeResolver();
function invokeResolver(resolver, promise) {
function resolvePromise(value) {
resolve(promise, value); // p2, p1
}
function rejectPromise(reason) {
reject(promise, reason);
}
try {
resolver(resolvePromise, rejectPromise);
} catch(e) {
rejectPromise(e);
}
}
invokeResolver爲關鍵的一環。在這裏,構造Promise對象時所傳入的方法會被執行,並將執行方法所需的兩個回調參數resolvePromise和rejectPromise傳了進去,方便業務代碼通知Promise對象執行結果。
如果我們的代碼很簡單的執行了一行代碼,例如:
resolve('Hello');
那麼resolvePromise會緊接着調用resolve方法。我們進入resolve方法一探究竟:
function resolve(promise, value) { // promise爲新構造的Promise對象,value = 'Hello'.
if (promise === value) {
fulfill(promise, value);
} else if (!handleThenable(promise, value)) {
fulfill(promise, value);
}
}
我們現在的邏輯會直接進入fulfill方法:
function fulfill(promise, value) {// promise爲新構造的Promise對象,value = 'Hello'.
if (promise._state !== PENDING) { return; } // 當前不滿足,默認爲PENDING狀態
promise._state = SEALED;// promise._state = SEALED
promise._detail = value;// promise._detail = 'Hello'
config.async(publishFulfillment, promise);
}
到這裏,將結果值賦值給了Promise對象。也就是說Promise對象保留了計算後的結果值’Hello’。然後我們看一下config.async()是個什麼鬼:
function asap(callback, arg) {
var length = queue.push([callback, arg]);
if (length === 1) {
// If length is 1, that means that we need to schedule an async flush.
// If additional callbacks are queued before the queue is flushed, they
// will be processed by this flush that we are scheduling.
scheduleFlush();
}
}
上面這段代碼位於lib/promise/asap.js
中。它主要用來將任務callback加入下一個時間片中。執行到這裏會將[publishFulfillment, promise]
組成一個數組放在隊列中,然後緊接着根據平臺進行任務刷新,也就是執行scheduleFlush();
不過,不管scheduleFlush方法再怎麼快,它也是被放在了所有事件最後才執行。所以接下來執行的代碼是主線程繼續執行的代碼:
promiseObj.then(function (value) {
// do sth...
}, function (error) {
// deal excption...
});
promiseObj.catch(function (error) {
// catch excption...
});
到這裏我們需要看看Promise.then方法是怎麼執行的:
then: function(onFulfillment, onRejection) {
var promise = this;
var thenPromise = new this.constructor(function() {});
if (this._state) {
var callbacks = arguments;
config.async(function invokePromiseCallback() {
invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
});
} else {
subscribe(this, thenPromise, onFulfillment, onRejection);
}
return thenPromise;
},
在調用then方法時,產生了一個新的Promise對象thenPromise,它會參與後面的任務執行。現在我們根據上下文進入subscribe方法:
function subscribe(parent, child, onFulfillment, onRejection) { // parent = customPromiseObject, child = thenPromise, onFulfillment = 成功回調,onRejection = 失敗回調
var subscribers = parent._subscribers;
var length = subscribers.length;
subscribers[length] = child;
subscribers[length + FULFILLED] = onFulfillment;
subscribers[length + REJECTED] = onRejection;
}
subscribers初始化是一個空數組,所以在這裏執行完畢後,將會是以下效果:
subscribers[0] = thenPromise;
subscribers[1] = 成功回調;
subscribers[2] = 失敗回調;
promise._subscribers = subscribers;
由於我們的示例代碼使用了catch,所以貼一下catch方法的源代碼:
'catch': function(onRejection) {
return this.then(null, onRejection);
}
所以在執行完then方法以及catch方法之後,promise._ subscribers內部如下:
subscribers[0] = thenPromise;
subscribers[1] = 成功回調;
subscribers[2] = 失敗回調;
subscribers[3] = thenPromise2;
subscribers[4] = null;
subscribers[5] = catch回調;
到這裏,我們的同步事件就執行完了。接下來開始分析異步事件。我們回到lib/promise/asap.js文件。
在Promise早期的源代碼中,對Promise的運行平臺做了區分。我們這裏對這塊細節不做深究,但不管是哪個平臺,最終還是會執行到flush方法。這裏需要注意:在執行flush方法時,它已經在所有事件執行之後了。這裏的所有事件指的是已經進入主線程隊列的事件,也就是剛剛執行完成的同步事件。
function flush() {
for (var i = 0; i < queue.length; i++) {
var tuple = queue[i];
var callback = tuple[0], arg = tuple[1];
callback(arg);
}
queue = [];
}
flush方法在這裏會回調剛剛傳入的方法publishFulfillment,參數爲那個promise對象。我們確認一下publishFulfillment方法的細節:
function publishFulfillment(promise) {
publish(promise, promise._state = FULFILLED);
}
它這裏很簡單,直接調用了publish方法。不過在這裏,promise對象的狀態再一次被改變了。從PENDING -> SEALED -> FULFILLED。我們進入publish:
function publish(promise, settled) { // promise = new Promise, settled = FULFILLED
var child, callback, subscribers = promise._subscribers, detail = promise._detail;
for (var i = 0; i < subscribers.length; i += 3) {
child = subscribers[i];
callback = subscribers[i + settled];
invokeCallback(settled, child, callback, detail);
}
promise._subscribers = null;
}
上面的代碼開始Promise對象的觀察者進行通知。注意這裏的for循環只循環了兩次。第一次循環:
child == thenPromise;
callback == 成功回調;
我們先來看正常的執行,這裏直接調用了invokeCallback方法:
function invokeCallback(settled, promise, callback, detail) { settled = FULFILLED, promise = thenPromise, callback = 成功回調, detail = 'Hello'
// 檢測callback是否是一個方法
var hasCallback = isFunction(callback),
value, error, succeeded, failed;
// 如果是方法,則執行
if (hasCallback) {
try {
value = callback(detail);
succeeded = true;
} catch(e) {
failed = true;
error = e;
}
} else {
value = detail;
succeeded = true;
}
if (handleThenable(promise, value)) {
return;
} else if (hasCallback && succeeded) {
resolve(promise, value);
} else if (failed) {
reject(promise, error);
} else if (settled === FULFILLED) {
resolve(promise, value);
} else if (settled === REJECTED) {
reject(promise, value);
}
}
咱們的示例比較簡單,在檢測回調方法是一個方法之後,就直接調用了。我們的示例也沒有返回值。所以直接走進了hasCallback && succeeded的判斷中,然後進入resolve方法。
function resolve(promise, value) { // promise = thenPromise, value = undefined
if (promise === value) {
fulfill(promise, value);
} else if (!handleThenable(promise, value)) {
fulfill(promise, value);
}
}
根據上下文,這裏進入了fulfill方法:
function fulfill(promise, value) {// promise = thenPromise, value = null
if (promise._state !== PENDING) { return; }
promise._state = SEALED;// thenPromise._state = SEALED
promise._detail = value;// thenPromise._detail = null
config.async(publishFulfillment, promise);
}
到這裏是不是似曾相識?沒錯,我們在上面已經見過上面兩個方法。不過在這裏是要通過flush回調執行thenPromise對象。然後根據上面提到的邏輯,最後thenPromise將會進入publish:
function publish(promise, settled) { // promise = thenPromise, settled = FULFILLED
var child, callback, subscribers = promise._subscribers, detail = promise._detail;
for (var i = 0; i < subscribers.length; i += 3) {
child = subscribers[i];
callback = subscribers[i + settled];
invokeCallback(settled, child, callback, detail);
}
promise._subscribers = null;
}
不過thenPromise對象是一個空對象,它的_subscribers屬性是一個空數組。所以這裏自然執行完畢。
不過到這裏也纔是thenPromise執行完畢,我們自己構造的Promise對象呢?它才準備進行第二次for循環:
child == thenPromise2;
callback == null;
然後根據上下文,在執行到invokeCallback方法,不過最終它的命運匹配到了settled === FULFILLED的條件,進入了resolve方法:
function resolve(promise, value) { // promise = new Promise, value = undefined
if (promise === value) {
fulfill(promise, value);
} else if (!handleThenable(promise, value)) {
fulfill(promise, value);
}
}
再進入fulfill方法:
function fulfill(promise, value) {
if (promise._state !== PENDING) { return; }
promise._state = SEALED;
promise._detail = value;
config.async(publishFulfillment, promise);
}
不過執行到這裏,promise的_state已經爲SEALED了,不等於PENDING,所以Promise也就自然終結了。
至此,一個普通的Promise執行終結。