本做品采用知识共享署名 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链条中。