本做品採用知識共享署名 4.0 國際許可協議進行許可。轉載聯繫做者並保留聲明頭部與原文連接https://luzeshu.com/blog/bluebirdsource
本博客同步在http://www.cnblogs.com/papertree/p/7163870.htmljavascript
時隔一年,把以前結尾還有一部分未完成的博客完成。版本2.9。具體忘了哪一個revision number。不過原理差很少。html
1. promise鏈是如何實現的?
2. promise對象如何變成fulfill狀態,並觸發promise鏈條的後續函數?new Promise和Promise.resolve() 有何不一樣?
*3. 爲何每執行.then就建立一個新的Promise對象,而不能使用第一個promise依次.then?
4. 如何對throw Error 和 reject進行加工java
第3個問題不是在使用Promise的過程當中提出的問題,而是看源碼過程當中針對源碼實現提出的問題。node
分兩條主線來說解:
第3節:回調函數的設置、promise鏈條的保存
第4節:promise對象的解決(設置爲rejected、fulfilled)、鏈條的遷移es6
咱們知道promise對象有Pendding、Fulfilled、Rejected三種狀態。Fulfilled和Rejected都屬於Settled。
在Promise對象內部,是經過this._bitField屬性的不一樣位來保存狀態信息的。
在Promise內部實現中,遠不止使用它時僅有的三種狀態,_bitField保存了不少其餘的狀態信息。好比Following、Followed、Migrated等等。算法
看一下內部置位、以及判斷是否置位等的函數。實際上都是位操做。數組
Promise.prototype._length = function () { return this._bitField & 131071; }; Promise.prototype._isFollowingOrFulfilledOrRejected = function () { return (this._bitField & 939524096) > 0; }; Promise.prototype._isFollowing = function () { return (this._bitField & 536870912) === 536870912; }; Promise.prototype._setLength = function (len) { this._bitField = (this._bitField & -131072) | (len & 131071); }; Promise.prototype._setFulfilled = function () { this._bitField = this._bitField | 268435456; }; Promise.prototype._setRejected = function () { this._bitField = this._bitField | 134217728; }; Promise.prototype._setFollowing = function () { this._bitField = this._bitField | 536870912; }; Promise.prototype._setIsFinal = function () { this._bitField = this._bitField | 33554432; }; Promise.prototype._isFinal = function () { return (this._bitField & 33554432) > 0; }; Promise.prototype._cancellable = function () { return (this._bitField & 67108864) > 0; }; Promise.prototype._setCancellable = function () { this._bitField = this._bitField | 67108864; }; Promise.prototype._unsetCancellable = function () { this._bitField = this._bitField & (~67108864); }; Promise.prototype._setIsMigrated = function () { this._bitField = this._bitField | 4194304; }; Promise.prototype._unsetIsMigrated = function () { this._bitField = this._bitField & (~4194304); }; Promise.prototype._isMigrated = function () { return (this._bitField & 4194304) > 0; };
咱們都知道設置一個promise鏈是經過promise對象的.then方法註冊fulfill、reject 狀態被激活時的回調函數。來看一下.then的代碼:promise
上圖能夠看到.then內部調用了._then,而後把咱們傳給.then()函數的didFulfill、didReject等回調函數經過_addCallbacks保存下來。這裏注意到,不是經過 「 this._addCallbacks() 」,而是經過 「 target._addCallbacks() 」,並且上一行還判斷了 「 target !== this 」的條件。那麼target是什麼呢?待會3.5節講。app
看到 _addCallbacks的實現,promise對象以每5個參數爲一組保存。當對一個promise對象調用一次.then(didFulfill, didReject)的時候,這組相關的參數保存在:異步
this._fulfillmentHandler0; // promise對象被置爲fulfilled 時的回調函數 this._rejectionHandler0; // promise對象被置爲rejected 時的回調函數。在3.3.1.1中知道,這個能夠用來保存followee this._progressHandler0; this._promise0; this._receiver0; // 當 fulfill被調用時 ,傳給函數的 this對象
當在一個promise對象上超過一次調用.then(didFulfill, didReject) 時,大於1的部分以這種形式保存在promise對象上:
var base; // base表示每組參數的起點,每5個參數爲一組保存 this[base + 0]; this[base + 1]; this[base + 2]; this[base + 3]; this[base + 4];
不少說明文檔會給出這樣的示例代碼:
// 來自 http://liubin.org/promises-book/#ch2-promise.then // promise能夠寫成方法鏈的形式 aPromise.then(function taskA(value){ // task A }).then(function taskB(vaue){ // task B }).catch(function onRejected(error){ console.log(error); });
這樣的實現的任務塊是這樣一種拓撲結構:
而對於另外一種拓撲結構的任務,有all 和 race方法:
若是沒有深究,咋一看可能覺得上面的「代碼3-3」中,依次.then都是在同一個aPromise對象上,而.then所註冊的多個回調函數都保存在aPromise上。
事實上,看到上面圖3-1中,Promise.prototype._then的代碼裏面,每次執行_then都會新建一個Promise對象,好比代碼3-3實際上等效於這樣:
var bPromise = aPromise.then(function taskA(value){ // task A }); var cPromise = bPromise.then(function taskB(vaue){ // task B }).catch(function onRejected(error){ console.log(error); });
aPromise、bPromise、cPromise分別是不一樣的對象。
那麼爲何這麼實現呢?想一下就會知道這樣多種拓撲結構:
當在同一個promise對象上屢次執行.then時,跟代碼3-3依次.then的狀況並不同,以下的示例代碼:
var bPromise = aPromise.then(function taskA(value){ // task A return new Promise(function (resolve) { setTimeout(function () { return resolve(); }, 5000); }); }); var cPromise = aPromise.then(function taskB(vaue){ // task B console.log('task B'); });
這裏用aPromise.then兩次,註冊兩個onFulfill函數(function taskA 和 function taskB)。當task A 裏返回新建的promise對象處於pending狀態時,task B的任務會先執行。
那麼這樣的promise鏈條是至關靈活的,能夠實現任何網狀的依賴關係。那麼經過這個發現,我想到利用它來作一件有趣的事情,能夠求有向圖最短路徑的值,看3.3節。
如上這個有向圖,要求0到3的最短路徑,那麼你可能第一想到的是Dijkstra算法、Floyd算法等等。
那麼利用promise在3.2節中講的特性,恰好能夠用來求最短路徑的值。但這裏只是求值(玩玩),不能代替「最短路徑算法」。上代碼:
1 var Promise = require('bluebird'); 2 3 var _base = 10; // 等待時間基數 4 5 var dot0 = new Promise(function (resolve) { 6 return resolve('0'); 7 }); 8 9 var dot0_2 = dot0.then(function () { 10 return new Promise(function (resolve) { 11 setTimeout(function() { 12 return resolve('0'); 13 }, 5 * _base); 14 }); 15 }); 16 17 var dot0_3 = dot0.then(function () { 18 return new Promise(function(resolve) { 19 setTimeout(function () { 20 return resolve('0'); 21 }, 30 * _base); 22 }); 23 }); 24 25 var dot2 = Promise.race([dot0_2]); 26 27 var dot2_1 = dot2.then(function (which) { 28 return new Promise(function (resolve) { 29 setTimeout(function () { 30 return resolve(which + ' 2'); 31 }, 15 * _base); 32 }); 33 }); 34 35 var dot2_5 = dot2.then(function (which) { 36 return new Promise(function (resolve) { 37 setTimeout(function () { 38 return resolve(which + ' 2'); 39 }, 7 * _base); 40 }); 41 }); 42 43 var dot5 = Promise.race([dot2_5]); 44 45 var dot5_3 = dot5.then(function (which) { 46 return new Promise(function (resolve) { 47 setTimeout(function () { 48 return resolve(which + ' 5'); 49 }, 10 * _base); 50 }); 51 }); 52 53 var dot5_4 = dot5.then(function (which) { 54 return new Promise(function (resolve) { 55 setTimeout(function () { 56 return resolve(which + ' 5'); 57 }, 18 * _base); 58 }); 59 }); 60 61 var dot1 = Promise.race([dot2_1]); 62 63 var dot1_4 = dot1.then(function (which) { 64 return new Promise(function (resolve) { 65 setTimeout(function () { 66 return resolve(which + ' 1'); 67 }, 8 * _base); 68 }); 69 }); 70 71 var dot4 = Promise.race([dot1_4, dot5_4]); 72 73 var dot4_3 = dot4.then(function (which) { 74 return new Promise(function (resolve) { 75 setTimeout(function () { 76 return resolve(which + ' 4'); 77 }, 4 * _base); 78 }); 79 }); 80 81 var dot3 = Promise.race([dot0_3, dot4_3, dot5_3]) 82 .then(function (str) { 83 console.log('result: ', str + ' 3'); 84 });
// 輸出結果:
// 0 2 5 3
若是咱們把2->1邊的權值改爲4,即把第31行代碼的15改爲4,那麼輸出結果會是 : 0 2 1 4 3
換種寫法(結果同樣):
1 var Promise = require('bluebird'); 2 3 var _base = 10; 4 // key表示頂點,值表示出邊 5 var digram = { 6 '0': { '2': 5, '3': 30 }, 7 '2': { '1': 15, '5': 7 }, 8 '5': { '3': 10, '4': 18 }, 9 '1': { '0': 2, '4': 8 }, 10 '4': { '3': 4 }, 11 '3': {} 12 }; 13 var order = ['0', '2', '5', '1', '4', '3']; 14 var startDot = '0'; 15 var endDot = '3'; 16 17 var promiseMap = {}; 18 function _buildMap() { 19 for(var dot in digram) 20 promiseMap[dot] = {_promise: undefined, _in: [], _out: []}; 21 for(var i = 0 ; i < order.length; ++i) { // 這裏不能用 for(var dot in digram),由於js對map的key會排序,這樣取出來的dot順序是0、一、二、三、四、5 22 var dot = order[i]; 23 if (dot == startDot) { 24 promiseMap[dot]._promise = Promise.resolve(); 25 } else if (dot == endDot) { 26 var localdot = dot; 27 promiseMap[dot]._promise = Promise.race(promiseMap[dot]._in) 28 .then(function (str) { 29 console.log('result: ', str + ' ' + localdot); 30 }); 31 continue; 32 } else { 33 debugger; 34 promiseMap[dot]._promise = Promise.race(promiseMap[dot]._in); 35 } 36 for(var edge in digram[dot]) { 37 var edgePromise = 38 promiseMap[dot]._promise.then(function (which) { 39 var self = this; 40 return new Promise(function (resolve) { 41 setTimeout(function () { 42 return resolve( (which ? which + ' ' : '') + self.dot); 43 }, digram[self.dot][self.edge] * _base); // 這裏不能直接訪問外層dot、edge,由於異步函數被調用的時候值已經被改變,也沒法經過for循環裏面保存tmpdot、tmpedge的辦法,由於js沒有塊級做用域,es6新標準有塊級做用域 44 }); 45 }.bind({dot: dot, edge: edge})); 46 promiseMap[dot]._out.push(edgePromise); 47 promiseMap[edge]._in.push(edgePromise); 48 } 49 } 50 } 51 _buildMap();
// 輸出結果:
// 0 2 5 3
那麼經過3.一、3.2節的理解,咱們知道了,一個.then鏈條裏面的結構並非這樣:
這是在同一個promise對象上屢次.then的狀況(代碼3-5)。
而依次.then的鏈條(代碼3-3 / 代碼3-4)是這樣的:
就是說若是這樣的代碼,不使用同一個promise對象,去.then兩次,那麼3.1中_addCallbacks的結構只會用到【this._promise0、】這一組,而不會有【this[base + index]】這些數據。
3.1節留了一個疑問,在調用promise.then註冊一個回調函數的時候,不是經過「 this._addCallbacks() 」 而是經過 「target._addCallbacks() 」,那麼這個target是什麼?
經過上幾節,瞭解了內部鏈條保存的細節,如今來看一下target。
看個示例代碼:
那麼經過app2.js,能夠看到通常狀況下,aPromise._target() 取到的target是this對象。經過target(aPromise)調用_addCallbacks時,bPromise是存在aPromise._promise0裏面的。
經過app3.js,能夠發現,當對aPromise使用一個pending狀態的cPromise對象進行resolve時,aPromise._target()取到的target會變成cPromise,後續經過aPromise.then所建立的bPromise對象也都是經過target(cPromise)進行_addCallbacks的,這個時候aPromise._promise0就是undefined,而cPromise._promise0就是bPromise。
那麼這裏target的變更與promise鏈條的遷移如何實現呢?這裏涉及到解決(settle)一個promise對象的細節,第4.3.1.1節會再講到。
看下示例代碼:
var Promise = require('bluebird'); var aPromise = new Promise(function (resolve) { return resolve(); // resolve的調用可能在任何異步回調函數裏面 }); var bPromise = aPromise.then(function () { var dPromise = Promise.resolve(); return dPromise; }); var cPromise = bPromise.then(function () { console.log('cPromise was resolved'); });
這幾種狀況的細節在4.3節講。
async是Promise用來管理promise鏈中全部promise對象的settle 的一個單例對象,在async.js文件:
async提供兩個接口:
關於this._schedule(),視條件可能有不少種狀況,可能不用異步,可能經過process.nextTick(),可能經過setTimeout(fn, 0),可能經過setImmediate(fn) 等等。
schedule的各類實如今schedule.js文件。
在4.3.2講解的例子中,就是經過process.nextTick()實現的。
針對4.1節講的幾種狀況,進行詳細說明。
來看代碼:
看右上角的示例代碼,右下角是輸出結果,爲何「step 2」不是在「step 1」以前呢?能夠知道構造一個Promise對象時,傳進去的函數(也即源碼裏面的resolver)是被同步執行的(若是是異步執行的,那麼「step 1」一定在「step 2」以後),這也意味着,若是在該回調函數裏面同步調用resolve(),那麼該Promise對象被建立以後就已是fulfilled狀態了。【看step 2 的輸出】。
能夠從左邊的源碼看到,傳給構造函數的回調函數是被同步執行的。
能夠看出構造函數的參數 —— resolver回調函數「step 1」被調用的時機。
那段代碼有點繞,能夠來剖析一下。
Promise.prototype._resolveFromResolver = function (resolver) { ... var r = tryCatch(resolver)(function(value) { if (promise === null) return; promise._resolveCallback(value); promise = null; }, function (reason) { if (promise === null) return; promise._rejectCallback(reason, synchronous); promise = null; }); ... };
這裏的tryCatch()暫時忽略它,下面講捕獲異常時講到。這裏徹底能夠當作:
var r = resolver(function(value) { if (promise === null) return; promise._resolveCallback(value); promise = null; }, function (reason) { if (promise === null) return; promise._rejectCallback(reason, synchronous); promise = null; });
resolver就是咱們new Promise時傳進去的回調函數:
var aPromise = new Promise(function (resolve) { console.log('step 1'); return resolve(); });
而咱們傳進去的回調函數的resolve參數,是bluebird調用resolver時傳出來給咱們的回調函數:
function(value) { if (promise === null) return; promise._resolveCallback(value); promise = null; }
同理,代碼4-3中那個function (reason) {} 也即平時new Promise(function (resolve, reject) {}) 時傳出來的reject函數。
這樣,當咱們new Promise時,在傳進去的resolver裏面調用resolve()時(不論是同步仍是在異步回調函數裏面),實際上就是調用了代碼4-5這個函數。
而value就是咱們調用resolve()時傳進去的解決值,這個值能夠被傳遞給.then()註冊的回調函數的參數。
因此resolve()實際上調用的是promise._resolveCallback(value)。在這個函數裏面,去修改當前promise對象的狀態爲fulfilled。
在3.5中,aPromise的resolver裏面,最終是經過resolve(cPromise) 去解決aPromise的,而這個cPromise是一個處於pending狀態的promise對象。
而後就說到aPromise._target() 變成了cPromise。而且後續經過aPromise.then()註冊進去的鏈條都掛在cPromise對象上。
那麼resolve(cPromise)實際上就是aPromise._resolveCallback(value)中的value=cPromise。
Promise.prototype._resolveCallback = function(value, shouldBind) { if (this._isFollowingOrFulfilledOrRejected()) return; if (value === this) return this._rejectCallback(makeSelfResolutionError(), false, true); var maybePromise = tryConvertToPromise(value, this); if (!(maybePromise instanceof Promise)) return this._fulfill(value); var propagationFlags = 1 | (shouldBind ? 4 : 0); this._propagateFrom(maybePromise, propagationFlags); var promise = maybePromise._target(); if (promise._isPending()) { var len = this._length(); for (var i = 0; i < len; ++i) { promise._migrateCallbacks(this, i); } this._setFollowing(); this._setLength(0); this._setFollowee(promise); } else if (promise._isFulfilled()) { this._fulfillUnchecked(promise._value()); } else { this._rejectUnchecked(promise._reason(), promise._getCarriedStackTrace()); } }; ... Promise.prototype._fulfill = function (value) { if (this._isFollowingOrFulfilledOrRejected()) return; this._fulfillUnchecked(value); }; ... Promise.prototype._fulfillUnchecked = function (value) { if (value === this) { var err = makeSelfResolutionError(); this._attachExtraTrace(err); return this._rejectUnchecked(err, undefined); } this._setFulfilled(); this._settledValue = value; this._cleanValues(); if (this._length() > 0) { this._queueSettlePromises(); } };
能夠看到當value不是Promise時,直接return this._fulfill(value)。而且最終在_fulfillUnchecked()裏面_setFulfilled(),這是第二節的那些狀態設置和檢驗函數。
當value是pending狀態的Promise時,就會把當前的aPromise _setFollowing(),而且_setFollowee(cPromise)。(實際上這裏也並不必定是cPromise,若是cPromise還有其餘followee的話,這裏是先經過cPromise._target()取出來的cPromise所跟隨的最終promise對象。)
_setFollowing()也是第二節的狀態設置函數。_setFollowee()就是給當前promise對象設置一個跟隨對象。
看下面代碼,實際上就是this._rejectionHandler0。
而注意到this._target()函數,事實上不是返回一個屬性,而是判斷當前的promise是否是被設置成「following」狀態了,是的話返回「跟隨對象」,一直循環到最終那個promise。
Promise.prototype._target = function() { var ret = this; while (ret._isFollowing()) ret = ret._followee(); return ret; }; Promise.prototype._followee = function() { return this._rejectionHandler0; }; Promise.prototype._setFollowee = function(promise) { this._rejectionHandler0 = promise; };
再次看回_resolveCallback()的實現,當value是pending狀態的promise時,在給aPromise設置following狀態而且設置與cPromise的跟隨關係以前,還有一個cPromise._migrateCallbacks(aPromise, i)的過程。
這migrate的就是3.1中講的屢次.then()時保存的那對參數組,其中第四個參數是.then()時建立的promise。
如今follower(即aPromise)上.then()的後續參數組都被遷移到followee(即cPromise)上面。
並且這些被遷移的參數組中的第四個參數被_setIsMigrated()。
Promise.prototype._migrateCallbacks = function (follower, index) { var fulfill = follower._fulfillmentHandlerAt(index); var reject = follower._rejectionHandlerAt(index); var progress = follower._progressHandlerAt(index); var promise = follower._promiseAt(index); var receiver = follower._receiverAt(index); if (promise instanceof Promise) promise._setIsMigrated(); this._addCallbacks(fulfill, reject, progress, promise, receiver, null); };
那再來看上圖4-2的示例代碼中,經過aPromise.then()建立的bPromise對象。
咱們知道aPromise 變成fulfilled以後,經過aPromise.then註冊的bPromise也是會被settle的。而在aPromise.then的時候,aPromise自己已是fulfilled狀態的。那麼經過「step 3」的輸出、以及「step 3」和「step 4」的順序,能夠知道經過.then()建立的promise對象的onFulfilled函數是被異步執行的(無論.then的時候aPromise是否fulfilled),並且經過「step 5」的輸出,咱們能夠猜到這個異步大體也是經過process.nextTick() 處理的。
在圖3-1中知道aPromise.then()最終調用了async.invoke(target._settlePromiseAtPostResolution, target, callbackIndex);
這個callbackIndex就是promise鏈條的index。
而從4.2中,知道async.invoke()最終致使了該回調函數被經過process.nextTick()異步執行了。
同時能夠知道,step 5和step 4的順序是不必定的,由於經過setTimeout、setImmediate都不同。並且不一樣版本的node,這幾個函數的執行順序也不必定同樣。
4.3.2中講了aPromise爲已經fulfilled時,.then產生的後續promise對象在 async.invoke(target._settlePromiseAtPostResolution, target, callbackIndex)中經過process.nextTick進行settle。
那麼aPromise.then產生bPromise時,aPromise仍是pending狀態,這時後續的bPromise對象的settle要等到aPromise被手動resolve()時再觸發。
在代碼4-6中,知道aPromise對象被經過resolve(value) settle掉時,最終調用_fulfillUnchecked()。
裏面再調用了this._queueSettlePromises()。在這裏面,把後續的promise對象一一解決。
Promise.prototype._queueSettlePromises = function() { async.settlePromises(this); this._setSettlePromisesQueued(); };
一樣是經過async來管理。
Async.prototype.settlePromises = function(promise) { if (this._trampolineEnabled) { AsyncSettlePromises.call(this, promise); } else { this._schedule(function() { promise._settlePromises(); }); } };
在4.3.3 中解決了bPromise時,在async.settlePromises()裏面又反過來調用bPromise._settlePromises()。這會激發bPromise解決後續鏈條。
Promise.prototype._settlePromises = function () { this._unsetSettlePromisesQueued(); var len = this._length(); for (var i = 0; i < len; i++) { this._settlePromiseAt(i); } };
那麼結合4.3.1 - 4.3.4,咱們看這樣一個promise鏈的解決時機是怎樣的,示例代碼:
var aPromise = new Promise(function (resolve) { return resolve(); }) .then(function () { // 假設這裏建立的是bPromise // task B }) .then(function () { // 假設這裏建立的是cPromise // task C });
解決順序:
aPromise建立之時,同步執行了構造函數的回調函數,同步執行了resolve。這個是4.3.1節的狀況。
bPromise在建立的時候,aPromise已經爲fulfilled狀態,這時經過async.invoke(target._settlePromiseAtPostResolution, target, callbackIndex),把bPromise的settle任務放到process.nextTick。這個是4.3.2節的狀況。
cPromise在建立的時候,注意這裏cPromise不是經過aPromise.then產生的,而是bPromise.then產生的,那麼這個時候bPromise仍是pending狀態的,因此cPromise的settle任務是4.3.5節裏面的狀況。
在4.3.1中暫時忽略了tryCatch(),如今來看看實現。
在util.js文件:
var errorObj = {e: {}}; var tryCatchTarget; function tryCatcher() { try { var target = tryCatchTarget; tryCatchTarget = null; return target.apply(this, arguments); } catch (e) { errorObj.e = e; return errorObj; } } function tryCatch(fn) { tryCatchTarget = fn; return tryCatcher; }
因此在下面這段代碼裏面
Promise.prototype._resolveFromResolver = function (resolver) { ... var r = tryCatch(resolver)(function(value) { if (promise === null) return; promise._resolveCallback(value); promise = null; }, function (reason) { if (promise === null) return; promise._rejectCallback(reason, synchronous); promise = null; }); ... };
實際上tryCatch只是轉換了一下error的形勢。把throw 出來的error,變成了return 回來的一個自定義的errorObj。
這樣,若是你沒有捕獲異常,這裏面的異常也不會變成node的未捕獲異常,而是bluebird的內部機制幫你捕獲了。
而若是你沒有在promise鏈條的末端catch(),那麼bluebird幫你捕獲的未解決異常最終會輸出。
看下面示例代碼。
var aPromise = new Promise((resolve, reject) => { console.log(aaa.abc); }); 輸出: Unhandled rejection ReferenceError: abc is not defined at ..../app.js:7:15 at tryCatcher (..../node_modules/bluebird/js/main/util.js:26:23) at .......
若是手動.catch()再輸出:
var aPromise = new Promise((resolve, reject) => { console.log(aaa.abc); }).catch((err) => { console.error(err); }); 輸出: ReferenceError: abc is not defined at app.js:7:15 at tryCatcher (/...../node_modules/bluebird/js/main/util.js:26:23)
在resolver裏面返回error,也能夠經過return reject(err); 返回的異常也會出如今Promise鏈條中。