天天看點

自己動手寫cpu pdf_自己動手寫 Promise

這段時間在學習Promise,但始終不得要領。為了更好地了解Promise,我決定自己實作一個簡易版的Promise,以學習Promise工作原理。該工程名為ToyPromise,倉庫位址如下:

https://github.com/pandengyang/toypromise.git
           

ToyPromise包含了以下屬性和方法:

自己動手寫cpu pdf_自己動手寫 Promise

首先,看一下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鍊,示意圖如下:

自己動手寫cpu pdf_自己動手寫 Promise

圖中,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的示意圖如下:

自己動手寫cpu pdf_自己動手寫 Promise

最初,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的一些偏進階的用法。