本作品采用知識(shí)共享署名 4.0 國(guó)際許可協(xié)議進(jìn)行許可。轉(zhuǎn)載聯(lián)系作者并保留聲明頭部與原文鏈接https://luzeshu.com/blog/bluebirdsource
本博客同步在http://www.cnblogs.com/papertree/p/7163870.html


時(shí)隔一年,把之前結(jié)尾還有一部分未完成的博客完成。版本2.9。具體忘了哪個(gè)revision number。不過(guò)原理差不多。

1. 帶上幾個(gè)問(wèn)題看源碼

1. promise鏈?zhǔn)侨绾螌?shí)現(xiàn)的?
2. promise對(duì)象如何變成fulfill狀態(tài),并觸發(fā)promise鏈條的后續(xù)函數(shù)?new Promise和Promise.resolve() 有何不同?
*3. 為什么每執(zhí)行.then就創(chuàng)建一個(gè)新的Promise對(duì)象,而不能使用第一個(gè)promise依次.then?
4. 如何對(duì)throw Error 和 reject進(jìn)行加工

第3個(gè)問(wèn)題不是在使用Promise的過(guò)程中提出的問(wèn)題,而是看源碼過(guò)程中針對(duì)源碼實(shí)現(xiàn)提出的問(wèn)題。

分兩條主線來(lái)講解:
第3節(jié):回調(diào)函數(shù)的設(shè)置、promise鏈條的保存
第4節(jié):promise對(duì)象的解決(設(shè)置為rejected、fulfilled)、鏈條的遷移

我們知道promise對(duì)象有Pendding、Fulfilled、Rejected三種狀態(tài)。Fulfilled和Rejected都屬于Settled。
在Promise對(duì)象內(nèi)部,是通過(guò)this._bitField屬性的不同位來(lái)保存狀態(tài)信息的。
在Promise內(nèi)部實(shí)現(xiàn)中,遠(yuǎn)不止使用它時(shí)僅有的三種狀態(tài),_bitField保存了很多其他的狀態(tài)信息。比如Following、Followed、


Migrated等等。

看一下內(nèi)部置位、以及判斷是否置位等的函數(shù)。實(shí)際上都是位操作。

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鏈如何實(shí)現(xiàn) —— 注冊(cè)階段(.then)

我們都知道設(shè)置一個(gè)promise鏈?zhǔn)峭ㄟ^(guò)promise對(duì)象的.then方法注冊(cè)fulfill、reject 狀態(tài)被激活時(shí)的回調(diào)函數(shù)。來(lái)看一下.then的代碼:


圖3-1


3.1 promise保存鏈條的結(jié)構(gòu)

上圖可以看到.then內(nèi)部調(diào)用了._then,然后把我們傳給.then()函數(shù)的didFulfill、didReject等回調(diào)函數(shù)通過(guò)_addCallbacks保存下來(lái)。這里注意到,不是通過(guò) “ this._addCallbacks() ”,而是通過(guò) “ target._addCallbacks() ”,而且上一行還判斷了 “ target !== this ”的條件。那么target是什么呢?待會(huì)3.5節(jié)講。

看到 _addCallbacks的實(shí)現(xiàn),promise對(duì)象以每5個(gè)參數(shù)為一組保存。當(dāng)對(duì)一個(gè)promise對(duì)象調(diào)用一次.then(didFulfill, didReject)的時(shí)候,這組相關(guān)的參數(shù)保存在:

this._fulfillmentHandler0;  // promise對(duì)象被置為fulfilled 時(shí)的回調(diào)函數(shù)this._rejectionHandler0;  // promise對(duì)象被置為rejected 時(shí)的回調(diào)函數(shù)。在3.3.1.1中知道,這個(gè)可以用來(lái)保存followeethis._progressHandler0;this._promise0;this._receiver0;  // 當(dāng) fulfill被調(diào)用時(shí)  ,傳給函數(shù)的 this對(duì)象
代碼3-1

當(dāng)在一個(gè)promise對(duì)象上超過(guò)一次調(diào)用.then(didFulfill, didReject) 時(shí),大于1的部分以這種形式保存在promise對(duì)象上:

var base; // base表示每組參數(shù)的起點(diǎn),每5個(gè)參數(shù)為一組保存this[base + 0];this[base + 1];this[base + 2];this[base + 3];this[base + 4];
代碼3-2


3.2 鏈條的拓?fù)浣Y(jié)構(gòu) —— 為何每個(gè).then 都new一個(gè)新的Promise對(duì)象?

很多說(shuō)明文檔會(huì)給出這樣的示例代碼:

// 來(lái)自 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

這樣的實(shí)現(xiàn)的任務(wù)塊是這樣一種拓?fù)浣Y(jié)構(gòu):


圖3-2


而對(duì)于另一種拓?fù)浣Y(jié)構(gòu)的任務(wù),有all 和 race方法:


圖3-3


如果沒(méi)有深究,咋一看可能以為上面的“代碼3-3”中,依次.then都是在同一個(gè)aPromise對(duì)象上,而.then所注冊(cè)的多個(gè)回調(diào)函數(shù)都保存在aPromise上。

事實(shí)上,看到上面圖3-1中,Promise.prototype._then的代碼里面,每次執(zhí)行_then都會(huì)新建一個(gè)Promise對(duì)象,比如代碼3-3實(shí)際上等效于這樣:

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分別是不同的對(duì)象。

那么為什么這么實(shí)現(xiàn)呢?想一下就會(huì)知道這樣多種拓?fù)浣Y(jié)構(gòu):


圖3-4


當(dāng)在同一個(gè)promise對(duì)象上多次執(zhí)行.then時(shí),跟代碼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兩次,注冊(cè)兩個(gè)onFulfill函數(shù)(function taskA 和 function taskB)。當(dāng)task A 里返回新建的promise對(duì)象處于pending狀態(tài)時(shí),task B的任務(wù)會(huì)先執(zhí)行。

那么這樣的promise鏈條是相當(dāng)靈活的,可以實(shí)現(xiàn)任何網(wǎng)狀的依賴關(guān)系。那么通過(guò)這個(gè)發(fā)現(xiàn),我想到利用它來(lái)做一件有趣的事情,可以求有向圖最短路徑的值,看3.3節(jié)。


3.3 利用promise的拓?fù)涮匦宰鲇腥さ氖?—— 有向圖的最短路徑另類求值


圖3-5


如上這個(gè)有向圖,要求0到3的最短路徑,那么你可能第一想到的是Dijkstra算法、Floyd算法等等。

那么利用promise在3.2節(jié)中講的特性,剛好可以用來(lái)求最短路徑的值。但這里只是求值(玩玩),不能代替“最短路徑算法”。上代碼:

 1 var Promise = require('bluebird');
 2 
 3 var _base = 10;  // 等待時(shí)間基數(shù)
 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

// 輸出結(jié)果:
// 0 2 5 3

如果我們把2->1邊的權(quán)值改成4,即把第31行代碼的15改成4,那么輸出結(jié)果會(huì)是 : 0 2 1 4 3

換種寫法(結(jié)果一樣):

 1 var Promise = require('bluebird');
 2 
 3 var _base = 10;
 4 // key表示頂點(diǎn),值表示出邊
 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),因?yàn)閖s對(duì)map的key會(huì)排序,這樣取出來(lái)的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);    // 這里不能直接訪問(wèn)外層dot、edge,因?yàn)楫惒胶瘮?shù)被調(diào)用的時(shí)候值已經(jīng)被改變,也無(wú)法通過(guò)for循環(huán)里面保存tmpdot、tmpedge的辦法,因?yàn)閖s沒(méi)有塊級(jí)作用域,es6新標(biāo)準(zhǔn)有塊級(jí)作用域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

// 輸出結(jié)果:

// 0 2 5 3


3.4 .then鏈條的結(jié)構(gòu)

那么通過(guò)3.1、3.2節(jié)的理解,我們知道了,一個(gè).then鏈條里面的結(jié)構(gòu)并不是這樣:


圖3-6


這是在同一個(gè)promise對(duì)象上多次.then的情況(代碼3-5)。

而依次.then的鏈條(代碼3-3 / 代碼3-4)是這樣的:


圖3-7


就是說(shuō)如果這樣的代碼,不使用同一個(gè)promise對(duì)象,去.then兩次,那么3.1中_addCallbacks的結(jié)構(gòu)只會(huì)用到【this._promise0、】這一組,而不會(huì)有【this[base + index]】這些數(shù)據(jù)。


3.5 Promise.prototype._target()

3.1節(jié)留了一個(gè)疑問(wèn),在調(diào)用promise.then注冊(cè)一個(gè)回調(diào)函數(shù)的時(shí)候,不是通過(guò)“ this._addCallbacks() ” 而是通過(guò) “target._addCallbacks() ”,那么這個(gè)target是什么?
通過(guò)上幾節(jié),了解了內(nèi)部鏈條保存的細(xì)節(jié),現(xiàn)在來(lái)看一下target。

看個(gè)示例代碼:


圖3-8


那么通過(guò)app2.js,可以看到一般情況下,aPromise._target() 取到的target是this對(duì)象。通過(guò)target(aPromise)調(diào)用_addCallbacks時(shí),bPromise是存在aPromise._promise0里面的。
通過(guò)app3.js,可以發(fā)現(xiàn),當(dāng)對(duì)aPromise使用一個(gè)pending狀態(tài)的cPromise對(duì)象進(jìn)行resolve時(shí),aPromise._target()取到的target會(huì)變成cPromise,后續(xù)通過(guò)aPromise.then所創(chuàng)建的bPromise對(duì)象也都是通過(guò)target(cPromise)進(jìn)行_addCallbacks的,這個(gè)時(shí)候aPromise._promise0就是undefined,而cPromise._promise0就是bPromise。

那么這里target的變動(dòng)與promise鏈條的遷移如何實(shí)現(xiàn)呢?這里涉及到解決(settle)一個(gè)promise對(duì)象的細(xì)節(jié),第4.3.1.1節(jié)會(huì)再講到。