本作品采用知識共享署名 4.0 國際許可協議進行許可。轉載聯系作者並保留聲明頭部與原文鏈接https://luzeshu.com/blog/bluebirdsource
本博客同步在http://www.cnblogs.com/papertree/p/7163870.html
時隔一年,把之前結尾還有一部分未完成的博客完成。版本2.9。具體忘了哪個revision number。不過原理差不多。
1. 帶上幾個問題看源碼
1. promise鏈是如何實現的?
2. promise對象如何變成fulfill狀態,並觸發promise鏈條的后續函數?new Promise和Promise.resolve() 有何不同?
*3. 為什么每執行.then就創建一個新的Promise對象,而不能使用第一個promise依次.then?
4. 如何對throw Error 和 reject進行加工
第3個問題不是在使用Promise的過程中提出的問題,而是看源碼過程中針對源碼實現提出的問題。
分兩條主線來講解:
第3節:回調函數的設置、promise鏈條的保存
第4節:promise對象的解決(設置為rejected、fulfilled)、鏈條的遷移
2. promise對象的狀態
我們知道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;
};
3. promise鏈如何實現 —— 注冊階段(.then)
我們都知道設置一個promise鏈是通過promise對象的.then方法注冊fulfill、reject 狀態被激活時的回調函數。來看一下.then的代碼:
3.1 promise保存鏈條的結構
上圖可以看到.then內部調用了._then,然后把我們傳給.then()函數的didFulfill、didReject等回調函數通過_addCallbacks保存下來。這里注意到,不是通過 “ this._addCallbacks() ”,而是通過 “ target._addCallbacks() ”,而且上一行還判斷了 “ target !== this ”的條件。那么target是什么呢?待會3.5節講。
看到 _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];
3.2 鏈條的拓撲結構 —— 為何每個.then 都new一個新的Promise對象?
很多說明文檔會給出這樣的示例代碼:
// 來自 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節。
3.3 利用promise的拓撲特性做有趣的事 —— 有向圖的最短路徑另類求值
如上這個有向圖,要求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、1、2、3、4、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.4 .then鏈條的結構
那么通過3.1、3.2節的理解,我們知道了,一個.then鏈條里面的結構並不是這樣:
這是在同一個promise對象上多次.then的情況(代碼3-5)。
而依次.then的鏈條(代碼3-3 / 代碼3-4)是這樣的:
就是說如果這樣的代碼,不使用同一個promise對象,去.then兩次,那么3.1中_addCallbacks的結構只會用到【this._promise0、】這一組,而不會有【this[base + index]】這些數據。
3.5 Promise.prototype._target()
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節會再講到。
4. promise對象的resolve細節 —— 解決階段(.resolve)
4.1 resolve一個promise對象的幾種情況
看下示例代碼:
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');
});
- 構造函數的回調函數里面,通過resolve()由我們手動觸發解決,例如上面的 aPromise。resolve可能在任何異步回調函數里面被調用。
- 通過Promise.resolve()創建一個已經被解決的promise對象
- then函數注冊的回調函數,會在上游promise對象被解決掉之后,由promise的機制觸發后續promise對象被解決。比如aPromise被resolve之后,bPromise、cPromise 由Promise的機制進行解決。
這幾種情況的細節在4.3節講。
4.2 Promise的隊列管理對象 —— async
async是Promise用來管理promise鏈中所有promise對象的settle 的一個單例對象,在async.js文件:
async提供兩個接口:
- settlePromises:接收一個promise對象,針對4.1節中的情況1,調用async.settlePromises去把要settle的promise對象入隊
- invoke:接收一個回調函數的相關參數,針對4.1節中的情況2,把被settle的上游promise中保存的回調函數(3.1節中的參數組)通過async.invoke,把要執行的回調函數通過this._schedule()去執行
關於this._schedule(),視條件可能有很多種情況,可能不用異步,可能通過process.nextTick(),可能通過setTimeout(fn, 0),可能通過setImmediate(fn) 等等。
schedule的各種實現在schedule.js文件。
在4.3.2講解的例子中,就是通過process.nextTick()實現的。
4.3 resolve一個promise鏈的細節
針對4.1節講的幾種情況,進行詳細說明。
4.3.1 構造函數里的resolver
來看代碼:
看右上角的示例代碼,右下角是輸出結果,為什么“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。
4.3.1.1 promise._resolveCallback() 與value
在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.3.2 .then注冊的回調函數被觸發的機制 —— aPromise.then時,已是fulfilled狀態
那再來看上圖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.3 .then注冊的回調函數被觸發的機制 —— aPromise.then時,處於pending狀態
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.4 .then注冊的回調函數被觸發的機制 —— bPromise.then時,bPromise本身就是.then產生的一個promise對象
在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.5 總結.then鏈條的解決
那么結合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節里面的情況。
5. tryCatch 處理
在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鏈條中。