Promise源码解析
Promise源碼解析
紙上得來終覺淺,絕知此事要躬行。之前只是很淺顯的知道Promise的用法,也大概猜測到它的內部是如何實現的。但是總是有一種不深究一下就不踏實的感覺。于是從npm上獲得早期的Promise源代碼,拿過來讀一讀,做一做筆記。
Promise的源碼寫的非常的巧妙,憑空閱讀會陷入入其中無法自拔。
簡單的Promise用法
一個簡單的Promise用法如下:
const promiseObj = new Promise(function (resolve, reject) {// 代碼執行resolve('value');//orreject('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 = FULFILLEDvar 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 = undefinedif (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 = undefinedif (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執行終結。
總結
以上是生活随笔為你收集整理的Promise源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Failed to execute go
- 下一篇: 转:掌握编程