總結JavaScript編程中的Promise使用

來源:互聯網
上載者:User

標籤:javascript   promise   

總結JavaScript編程中的Promise使用


Promise核心說明

儘管Promise已經有自己的規範,但目前的各類Promise庫,在Promise的實現細節上是有差異的,部分API甚至在意義上完全不同。但Promise的核心內容,是相通的,它就是then方法。在相關術語中,promise指的就是一個有then方法,且該方法能觸發特定行為的對象或函數。

Promise可以有不同的實現方式,因此Promise核心說明並不會討論任何具體的實現代碼。

先閱讀Promise核心說明的意思是:看,這就是需要寫出來的結果,請參照這個結果想一想怎麼用代碼寫出來吧。

起步:用這一種方式理解Promise (閱讀: http://www.aichengxu.com/view/62673 )

回想一下Promise解決的是什麼問題?回調。例如,函數doMission1()代表第一件事情,現在,我們想要在這件事情完成後,再做下一件事情doMission2(),應該怎麼做呢?

先看看我們常見的回調模式。doMission1()說:“你要這麼做的話,就把doMission2()交給我,我在結束後幫你調用。”所以會是:

doMission1(doMission2);


Promise模式又是如何呢?你對doMission1()說:“不行,控制權要在我這裡。你應該改變一下,你先返回一個特別的東西給我,然後我來用這個東西安排下一件事。”這個特別的東西就是Promise,這會變成這樣:

doMission1().then(doMission2);


可以看出,Promise將回調模式的主從關係調換了一個位置(翻身做主人!),多個事件的流程關係,就可以這樣集中到主幹道上(而不是分散在各個事件函數之內)。

好了,如何做這樣一個轉換呢?從最簡單的情況來吧,假定doMission1()的代碼是:

function doMission1(callback){  var value = 1;  callback(value);}


那麼,它可以改變一下,變成這樣:

function doMission1(){  return {    then: function(callback){      var value = 1;      callback(value);    }  };}


這就完成了轉換。雖然並不是實際有用的轉換,但到這裡,其實已經觸及了Promise最為重要的實現要點,即Promise將返回值轉換為帶then方法的對象。

進階:Q的設計路程

從defer開始

design/q0.js是Q初步成型的第一步。它建立了一個名為defer的工具函數,用於建立Promise:

var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      value = _value;      for (var i = 0, ii = pending.length; i < ii; i++) {        var callback = pending[i];        callback(value);      }      pending = undefined;    },    then: function (callback) {      if (pending) {        pending.push(callback);      } else {        callback(value);      }    }  }};


這段源碼可以看出,運行defer()將得到一個對象,該對象包含resolve和then兩個方法。請回想一下jQuery的Deferred(同樣有resolve和then),這兩個方法將會是近似的效果。then會參考pending的狀態,如果是等待狀態則將回調儲存(push),否則立即調用回調。resolve則將肯定這個Promise,更新值的同時運行完所有儲存的回調。defer的使用樣本如下:

var oneOneSecondLater = function () {  var result = defer();  setTimeout(function () {    result.resolve(1);  }, 1000);  return result;};


oneOneSecondLater().then(callback);

這裡oneOneSecondLater()包含非同步內容(setTimeout),但這裡讓它立即返回了一個defer()產生的對象,然後將對象的resolve方法放在非同步結束的位置調用(並附帶上值,或者說結果)。

到此,以上代碼存在一個問題:resolve可以被執行多次。因此,resolve中應該加入對狀態的判斷,保證resolve只有一次有效。這就是Q下一步的design/q1.js(僅差異部分):

resolve: function (_value) {  if (pending) {    value = _value;    for (var i = 0, ii = pending.length; i < ii; i++) {      var callback = pending[i];      callback(value);    }    pending = undefined;  } else {    throw new Error("A promise can only be resolved once.");  }}


對第二次及更多的調用,可以這樣拋出一個錯誤,也可以直接忽略掉。

分離defer和promise

在前面的實現中,defer產生的對象同時擁有then方法和resolve方法。按照定義,promise關心的是then方法,至於觸發promise改變狀態的resolve,是另一回事。所以,Q接下來將擁有then方法的promise,和擁有resolve的defer分離開來,各自獨立使用。這樣就好像劃清了各自的職責,各自只留一定的許可權,這會使代碼邏輯更明晰,易於調整。請看design/q3.js:(q2在此跳過)

var isPromise = function (value) {  return value && typeof value.then === "function";};var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      if (pending) {        value = _value;        for (var i = 0, ii = pending.length; i < ii; i++) {          var callback = pending[i];          callback(value);        }        pending = undefined;      }    },    promise: {      then: function (callback) {        if (pending) {          pending.push(callback);        } else {          callback(value);        }      }    }  };};


如果你仔細對比一下q1,你會發現區別很小。一方面,不再拋出錯誤(改為直接忽略第二次及更多的resolve),另一方面,將then方法移動到一個名為promise的對象內。到這裡,運行defer()得到的對象(就稱為defer吧),將擁有resolve方法,和一個promise屬性指向另一個對象。這另一個對象就是僅有then方法的promise。這就完成了分離。

前面還有一個isPromise()函數,它通過是否有then方法來判斷對象是否是promise(duck-typing的判斷方法)。為了正確使用和處理分離開的promise,會像這樣需要將promise和其他值區分開來。

實現promise的級聯

接下來會是相當重要的一步。到前面到q3為止,所實現的promise都是不能級聯的。但你所熟知的promise應該支援這樣的文法:

promise.then(step1).then(step2);


以上過程可以理解為,promise將可以創造新的promise,且取自舊的promise的值(前面代碼中的value)。要實現then的級聯,需要做到一些事情:

then方法必須返回promise。

這個返回的promise必須用傳遞給then方法的回調運行後的返回結果,來設定自己的值。

傳遞給then方法的回調,必須返回一個promise或值。

design/q4.js中,為了實現這一點,新增了一個工具函數ref:

var ref = function (value) {  if (value && typeof value.then === "function")    return value;  return {    then: function (callback) {      return ref(callback(value));    }  };};


這是在著手處理與promise關聯的value。這個工具函數將對任一個value值做一次封裝,如果是一個promise,則什麼也不做,如果不是promise,則將它封裝成一個promise。注意這裡有一個遞迴,它確保封裝成的promise可以使用then方法級聯。為了協助理解它,下面是一個使用的例子:

ref("step1").then(function(value){  console.log(value); // "step1"  return 15;}).then(function(value){  console.log(value); // 15});


你可以看到value是怎樣傳遞的,promise級聯需要做到的也是如此。

design/q4.js通過結合使用這個ref函數,將原來的defer轉變為可級聯的形式:

var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      if (pending) {        value = ref(_value); // values wrapped in a promise        for (var i = 0, ii = pending.length; i < ii; i++) {          var callback = pending[i];          value.then(callback); // then called instead        }        pending = undefined;      }    },    promise: {      then: function (_callback) {        var result = defer();        // callback is wrapped so that its return        // value is captured and used to resolve the promise        // that "then" returns        var callback = function (value) {          result.resolve(_callback(value));        };        if (pending) {          pending.push(callback);        } else {          value.then(callback);        }        return result.promise;      }    }  };};


原來callback(value)的形式,都修改為value.then(callback)。這個修改後效果其實和原來相同,只是因為value變成了promise封裝的類型,會需要這樣調用。

then方法有了較多變動,會先新產生一個defer,並在結尾處返回這個defer的promise。請注意,callback不再是直接取用傳遞給then的那個,而是在此基礎之上增加一層,並把新產生的defer的resolve方法放置在此。此處可以理解為,then方法將返回一個新產生的promise,因此需要把promise的resolve也預留好,在舊的promise的resolve運行後,新的promise的resolve也會隨之運行。這樣才能像管道一樣,讓事件按照then串連的內容,一層一層傳遞下去。

加入錯誤處理

promise的then方法應該可以包含兩個參數,分別是肯定和否定狀態的處理函數(onFulfilled與onRejected)。前面我們實現的promise還只能轉變為肯定狀態,所以,接下來應該加入否定狀態部分。

請注意,promise的then方法的兩個參數,都是選擇性參數。design/q6.js(q5也跳過)加入了工具函數reject來協助實現promise的否定狀態:

var reject = function (reason) {  return {    then: function (callback, errback) {      return ref(errback(reason));    }  };};


它和ref的主要區別是,它返回的對象的then方法,只會取第二個參數的errback來運行。design/q6.js的其餘部分是:

var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      if (pending) {        value = ref(_value);        for (var i = 0, ii = pending.length; i < ii; i++) {          value.then.apply(value, pending[i]);        }        pending = undefined;      }    },    promise: {      then: function (_callback, _errback) {        var result = defer();        // provide default callbacks and errbacks        _callback = _callback || function (value) {          // by default, forward fulfillment          return value;        };        _errback = _errback || function (reason) {          // by default, forward rejection          return reject(reason);        };        var callback = function (value) {          result.resolve(_callback(value));        };        var errback = function (reason) {          result.resolve(_errback(reason));        };        if (pending) {          pending.push([callback, errback]);        } else {          value.then(callback, errback);        }        return result.promise;      }    }  };};


這裡的主要改動是,將數組pending只儲存單個回調的形式,改為同時儲存肯定和否定的兩種回調的形式。而且,在then中定義了預設的肯定和否定回調,使得then方法滿足了promise的2個選擇性參數的要求。

你也許注意到defer中還是只有一個resolve方法,而沒有類似jQuery的reject。那麼,錯誤處理要如何觸發呢?請看這個例子:

var defer1 = defer(),promise1 = defer1.promise;promise1.then(function(value){  console.log("1: value = ", value);  return reject("error happens"); }).then(function(value){  console.log("2: value = ", value);}).then(null, function(reason){  console.log("3: reason = ", reason);});defer1.resolve(10);// Result:// 1: value = 10// 3: reason = error happens


可以看出,每一個傳遞給then方法的返回值是很重要的,它將決定下一個then方法的調用結果。而如果像上面這樣返回工具函數reject產生的對象,就會觸發錯誤處理。

融入非同步

終於到了最後的design/q7.js。直到前面的q6,還存在一個問題,就是then方法啟動並執行時候,可能是同步的,也可能是非同步,這取決於傳遞給then的函數(例如直接返回一個值,就是同步,返回一個其他的promise,就可以是非同步)。這種不確定性可能帶來潛在的問題。因此,Q的後面這一步,是確保將所有then轉變為非同步。

design/q7.js定義了另一個工具函數enqueue:

var enqueue = function (callback) {  //process.nextTick(callback); // NodeJS  setTimeout(callback, 1); // Na?ve browser solution};


顯然,這個工具函數會將任意函數延遲到下一個事件隊列運行。

design/q7.js其他的修改點是(只顯示修改部分):

var ref = function (value) {  // ...  return {    then: function (callback) {      var result = defer();      // XXX      enqueue(function () {        result.resolve(callback(value));      });      return result.promise;    }  };};var reject = function (reason) {  return {    then: function (callback, errback) {      var result = defer();      // XXX      enqueue(function () {        result.resolve(errback(reason));      });      return result.promise;    }  };};var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      // ...          enqueue(function () {            value.then.apply(value, pending[i]);          });      // ...    },    promise: {      then: function (_callback, _errback) {          // ...          enqueue(function () {            value.then(callback, errback);          });          // ...      }    }  };};


即把原來的value.then的部分,都轉變為非同步。

到此,Q提供的Promise設計原理q0~q7,全部結束。

結語

即便本文已經是這麼長的篇幅,但所講述的也只到基礎的Promise。大部分Promise庫會有更多的API來應對更多和Promise有關的需求,例如all()、spread(),不過,讀到這裡,你已經瞭解了實現Promise的核心理念,這一定對你今後應用Promise有所協助。

在我看來,Promise是精巧的設計,我花了相當一些時間才差不多理解它。Q作為一個典型Promise庫,在思路上走得很明確。可以感受到,再複雜的庫也是先從基本的要點開始的,如果我們自己要做類似的事,也應該保持這樣的心態一點一點進步。 

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

總結JavaScript編程中的Promise使用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.