這段時間在學習Promise,但始終不得要領。為了更好地了解Promise,我決定自己實作一個簡易版的Promise,以學習Promise工作原理。該工程名為ToyPromise,倉庫位址如下:
https://github.com/pandengyang/toypromise.git
ToyPromise包含了以下屬性和方法:
首先,看一下ToyPromise構造函數,代碼如下:
function ToyPromise(resolver, name) { this._status = "pending"; this._name = name; this._fullfilled = function dummyFullfilled(value) { return value; }; this._rejected = function dummyRejected(error) { throw error; }; resolver(this._resolve.bind(this), this._reject.bind(this));}
構造函數先進行了一系列的初始化,包括名稱、狀态、預設的完成/拒絕回調函數,代碼如下:
this._status = "pending"; this._name = name; this._fullfilled = function dummyFullfilled(value) { return value; }; this._rejected = function dummyRejected(error) { throw error; };
預設的完成回調函數将完成值重新傳回;預設的拒絕回調函數将拒絕原因重新抛出。
初始化後,構造函數立即執行使用者傳遞的resolver函數,并将_resolve和_reject方法傳遞給resolver。
resolver(this._resolve.bind(this), this._reject.bind(this));
resolver在決議/拒絕該ToyPromise時會調用該ToyPromise的_resolve/_reject方法。代碼如下:
var A = new ToyPromise(function resolver(resolve, reject) { var number = Math.random(); if (number <= 0.5) { resolve("a"); } else { reject(new Error("a")); }}, "A");
_resolve方法中的this指向所屬ToyPromise,如果直接将this._resolve作為參數傳遞給resolver,會發生this綁定丢失。this綁定丢失示例如下:
var name = "window";foo = { name: "foo", bar: function bar() { console.log(this.name); }}; foo.bar(); // foovar bar = foo.bar;bar(); // 輸出window,而不是foo,說明this丢失了
為了避免this綁定丢失,我們采用硬綁定的方式來傳遞_resolve和_reject函數。
resolver會根據執行結果_resolve/_reject這個ToyPromise。_resolve/_reject代碼如下:
ToyPromise.prototype._resolve = function resolve(value) { if (this._status != "pending") { return; } this._status = "fullfilled"; this._data = value; setTimeout(this._asyncFullfilled.bind(this), 0);};
ToyPromise.prototype._reject = function reject(error) { if (this._status != "pending") { return; } this._status = "rejected"; this._data = error; setTimeout(this._asyncRejected.bind(this), 0);};
首先,檢測該ToyPromise是否決議過,這樣可以保證ToyPromise隻被決議一次,通過then注冊的回調也隻會執行一次。代碼如下:
if (this._status != "pending") { return; }
然後,利用決議結果填充_status與_data。代碼如下:
this._status = "fullfilled"; this._data = value;
this._status = "rejected"; this._data = error;
最後,利用setTimeout異步調用通過then注冊的_fullfilled/_rejected回調函數。代碼如下:
setTimeout(this._asyncFullfilled.bind(this), 0);
setTimeout(this._asyncRejected.bind(this), 0);
如果不異步調用_fullfilled/_rejected,當在resolver中同步調用_resolve/_reject時,會出現回調函數調用過早的問題。例如:
var A = new ToyPromise(function resolver(resolve, reject) { var number = Math.random(); if (number <= 0.5) { resolve("a"); } else { reject(new Error("a")); }}, "A");A.then(/* callbacks */)
resolver同步調用_resolve/_reject,此時,ToyPromise的構造函數還未傳回,A.then也未調用。也就是說,使用者還未注冊回調函數,回調函數(預設的)就已經運作了。由于ToyPromise隻能被決議一次,A.then注冊的回調函數永遠也不會運作。
現在看一下then方法,then方法用于向ToyPromise注冊完成/拒絕回調函數。代碼如下:
ToyPromise.prototype.then = function(fullfilled, rejected, name) { this._fullfilled = fullfilled; this._rejected = rejected; nextPromise = new ToyPromise(function resolver(resolve, reject) {}, name); this._nextPromise = nextPromise; console.log("" + this._name + "'s nextPromise is " + name); return nextPromise;};
首先,為目前ToyPromise注冊_fullfilled/_rejected函數,代碼如下:
this._fullfilled = fullfilled;this._rejected = rejected;
然後,建立一個新的ToyPromise,并讓目前ToyPromise指向這個新的ToyPromise,然後傳回該ToyPromise。代碼如下:
nextPromise = new ToyPromise(function resolver(resolve, reject) {}, name); this._nextPromise = nextPromise; console.log("" + this._name + "'s nextPromise is " + name); return nextPromise;
當一個ToyPromise被決議時,除了執行_fullfilled/_rejected方法外,還需要決議由then傳回的ToyPromise,以此實作鍊式調用。是以,當ToyPromise調用then時,使用_nextPromise指向由then傳回的ToyPromise。代碼如下:
this._nextPromise = nextPromise;
由此,形成了一條ToyPromise鍊,示意圖如下:
圖中,A調用then時,會為A注冊完成/拒絕處理函數,然後A指向A.then傳回的B。B調用then時,會為B注冊完成/拒絕處理函數,然後B指向B.then傳回的C。進而形成一條ToyPromise鍊。
當ToyPromise被決議/拒絕時,會調用_resolve/_reject方法,然後異步調用_asyncFullfilled/_asyncRejected方法。_asyncFullfilled/_asyncRejected方法代碼如下:
ToyPromise.prototype._asyncFullfilled = function() { console.log("" + this._name + " " + this._status + ": " + this._data); if (!this._nextPromise) { return; } var result; try { console.log("call " + this._name + "'s _fullfiled"); result = this._fullfilled(this._data); } catch (error) { console.log( "reject next promise " + this._nextPromise._name + " by exception" ); this._nextPromise._reject(error); return; } if (result instanceof ToyPromise) { result._fullfilled = this._nextPromise._fullfilled; result._rejected = this._nextPromise._rejected; result._nextPromise = this._nextPromise._nextPromise; this._nextPromise = result; console.log( "" + this._name + "'s next promise is " + this._nextPromise._name ); console.log( "" + result._name + "'s next promise is " + result._nextPromise._name ); } else { console.log("resolve next promise " + this._nextPromise._name); this._nextPromise._resolve(result); }};
ToyPromise.prototype._asyncRejected = function() { console.log("" + this._name + " " + this._status + ": " + this._data); if (!this._nextPromise) { return; } var result; try { console.log("call " + this._name + "'s _rejected"); result = this._rejected(this._data); } catch (error) { console.log( "reject next promise " + this._nextPromise._name + "by exception" ); this._nextPromise._reject(error); return; } if (result instanceof ToyPromise) { result._fullfilled = this._nextPromise._fullfilled; result._rejected = this._nextPromise._rejected; result._nextPromise = this._nextPromise._nextPromise; this._nextPromise = result; console.log( "" + this._name + "'s next promise is " + this._nextPromise._name ); console.log( "" + result._name + "'s next promise is " + result._nextPromise._name ); } else { console.log("resolve next promise " + this._nextPromise._name); this._nextPromise._resolve(result); }};
首先,檢測該ToyPromise是否調用過then,調用了then之後,_nextPromise會被指派。代碼如下:
if (!this._nextPromise) { return;}
其次,執行使用者注冊的_fullfilled/_rejected函數。若執行過程中沒有異常,則使用傳回值決議_nextPromise;若執行過程中catch到異常,則使用該異常作為拒絕原因拒絕_nextPromise。代碼如下:
var result; try { console.log("call " + this._name + "'s _fullfiled"); result = this._fullfilled(this._data); } catch (error) { console.log( "reject next promise " + this._nextPromise._name + " by exception" ); this._nextPromise._reject(error); return; } if (result instanceof ToyPromise) { } else { console.log("resolve next promise " + this._nextPromise._name); this._nextPromise._resolve(result); }
try { console.log("call " + this._name + "'s _rejected"); result = this._rejected(this._data); } catch (error) { console.log( "reject next promise " + this._nextPromise._name + "by exception" ); this._nextPromise._reject(error); return; } if (result instanceof ToyPromise) { } else { console.log("resolve next promise " + this._nextPromise._name); this._nextPromise._resolve(result); }
從代碼中我們可以看出,ToyPromise鍊是交叉運作,并不是說第一個ToyPromise是fullfilled的,其後跟着的所有ToyPromise都是fullfilled。
若完成/拒絕回調函數傳回的是一個ToyPromise,則傳回的ToyPromise會替換掉由then傳回的ToyPromise。代碼如下:
if (result instanceof ToyPromise) { result._fullfilled = this._nextPromise._fullfilled; result._rejected = this._nextPromise._rejected; result._nextPromise = this._nextPromise._nextPromise; this._nextPromise = result; console.log( "" + this._name + "'s next promise is " + this._nextPromise._name ); console.log( "" + result._name + "'s next promise is " + result._nextPromise._name ); }
完成/拒絕回調函數傳回ToyPromise的示意圖如下:
最初,A.then傳回了B,A的_nextPromise指向B。若A的_fullfilled傳回了B_1,則将A的_nextPromise修正為B_1,B_1的_fullfilled、_rejected、_nextPromise分别指向B的同名字段。當B_1被決議時,會重新觸發ToyPromise鍊的運作。
ToyPromise僅僅用于示範Promise的基本工作原理,沒有考慮封裝以及Promise的一些偏進階的用法。