本作品采用知识共享署名 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)、链条的迁移

我们知道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) {http://www.cnblogs.com/papertree/p/7163870.html
    
    
    
    
    
    
    
    
    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


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中知道,这个可以用来保存followeethis._progressHandler0;this._promise0;this._receiver0;  // 当 fulfill被调用时  ,传给函数的 this对象
代码3-1

当在一个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


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);});
代码3-3

这样的实现的任务块是这样一种拓扑结构:


图3-2


而对于另一种拓扑结构的任务,有all 和 race方法:


图3-3


如果没有深究,咋一看可能以为上面的“代码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);});
代码3-4

aPromise、bPromise、cPromise分别是不同的对象。

那么为什么这么实现呢?想一下就会知道这样多种拓扑结构:


图3-4


当在同一个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');});
代码3-5

这里用aPromise.then两次,注册两个onFulfill函数(function taskA 和 function taskB)。当task A 里返回新建的promise对象处于pending状态时,task B的任务会先执行。

那么这样的promise链条是相当灵活的,可以实现任何网状的依赖关系。那么通过这个发现,我想到利用它来做一件有趣的事情,可以求有向图最短路径的值,看3.3节。


3.3 利用promise的拓扑特性做有趣的事 —— 有向图的最短路径另类求值


图3-5


如上这个有向图,要求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     });
代码3-6

// 输出结果:
// 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、522         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();
代码3-7

// 输出结果:

// 0 2 5 3


3.4 .then链条的结构

那么通过3.1、3.2节的理解,我们知道了,一个.then链条里面的结构并不是这样:


图3-6


这是在同一个promise对象上多次.then的情况(代码3-5)。

而依次.then的链条(代码3-3 / 代码3-4)是这样的:


图3-7


就是说如果这样的代码,不使用同一个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。

看个示例代码:


图3-8


那么通过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节会再讲到。