Promise到底解決了什麼問題?

個人github博客 https://github.com/zhuanyongxigua/blogjavascript

你們都知道Promise解決了回調地獄的問題。說到回調地獄,很容易想到下面這個容易讓人產生誤解的圖片java

可回調地獄究竟是什麼?它到底哪裏有問題?是由於嵌套很差看仍是讀起來不方便?node

首先咱們要想一想,嵌套到底哪裏有問題?git

舉個例子:程序員

function a() {
  function b() {
    function c() {
      function d() {}
      d();
    }
    c();
  }
  b();
}
a();

這也是嵌套,雖然好像不是特別美觀,可咱們並不會以爲這有什麼問題吧?由於咱們常常會寫出相似的代碼。github

在這個例子中的嵌套的問題僅僅是縮進的問題,而縮進除了會讓代碼變寬可能會形成讀代碼的一點不方便以外,並無什麼其餘的問題。若是僅僅是這樣,爲何不叫「縮進地獄」或「嵌套地獄」?面試

把回調地獄徹底理解成縮進的問題是常見的對回調地獄的誤解。要回到「回調地獄」這個詞語上面來,它的重點就在於「回調」,而「回調」在JS中應用最多的場景固然就是異步編程了。ajax

因此,「回調地獄」所說的嵌套實際上是指異步的嵌套。它帶來了兩個問題:可讀性的問題和信任問題。編程

可讀性的問題

這是一個在網上隨便搜索的關於執行順序的面試題:瀏覽器

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(new Date, i);
  }, 1000);
}

console.log(new Date, i);

答案是什麼你們本身想吧,這不是重點。重點是,你要想一下子吧?

一個整潔的回調:

listen( "click", function handler( evt){ 
  setTimeout( function request(){ 
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
  }, 500); 
});

若是異步的嵌套都是這樣乾淨整潔,那「回調地獄」給程序猿帶來的傷害立刻就會減小不少。

可咱們實際在寫業務邏輯的時候,真實的狀況應該是這樣的:

listen( "click", function handler(evt){ 
  doSomething1();
  doSomething2();
  doSomething3();
  doSomething4();
  setTimeout( function request(){ 
    doSomething8();
    doSomething9();
    doSomething10();
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
    doSomething11();
    doSomething12();
    doSomething13();
  }, 500); 
  doSomething5();
  doSomething6();
  doSomething7();
});

這些「doSomething」有些是異步的,有些是同步。這樣的代碼讀起來會很是的吃力,由於你要不停的思考他們的執行順序,而且還要記在腦殼裏面。這就是異步的嵌套帶來的可讀性的問題,它是由異步的運行機制引發的。

信任問題

這裏主要用異步請求討論。咱們在作AJAX請求的時候,通常都會使用一些第三方的工具庫(即使是本身封裝的,也能夠在必定程度上理解成第三方的),這就會帶來一個問題:這些工具庫是否百分百的可靠?

一個來自《YDKJS》的例子:一個程序員開發了一個付款的系統,它良好的運行了很長時間。忽然有一天,一個客戶在付款的時候信用卡被連續刷了五次。這名程序員在調查了之後發現,一個第三方的工具庫由於某些緣由把付款回調執行了五次。在與第三方團隊溝通以後問題獲得瞭解決。

故事講完了,可問題真的解決了嗎?是否還可以充分的信任這個工具庫?信任依然要有,可完善必要的檢查和錯誤處理勢在必行。當咱們解決了這個問題,因爲它的啓發,咱們還會聯想到其餘的問題,好比沒有調用回調。

再繼續想,你會發現,這樣的問題還要好多好多。總結一下可能會出現的問題:

  • 回調過早(通常是異步被同步調用);
  • 回調過晚或沒有回調;
  • 回調次數過多;
  • 等等

加上了這些檢查,強壯以後的代碼多是這樣的:

listen( "click", function handler( evt){ 
  check1();
  doSomething1();
  setTimeout( function request(){ 
    check2();
    doSomething3();
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
    doSomething4();
  }, 500); 
  doSomething2();
});

咱們都清楚的知道,實際的check要比這裏看起來的複雜的多,並且不少很難複用。這不但使代碼變得臃腫不堪,還進一步加重了可讀性的問題。

雖然這些錯誤出現的機率不大,但咱們依然必需要處理。

這就是異步嵌套帶來的信任問題,它的問題的根源在於控制反轉。控制反轉在面向對象中的應用是依賴注入,實現了模塊間的解耦。而在回調中,它就顯得沒有那麼善良了,控制權被交給了第三方,由第三方決定何時調用回調以及如何調用回調。

一些解決信任問題的嘗試

加一個處理錯誤的回調

function success(data) { 
  console. log(data); 
} 
function failure(err) { 
  console. error( err ); 
} 
ajax( "http:// some. url. 1", success, failure );

nodejs的error-first

function response(err, data) { 
  if (err) { 
    console. error( err ); 
  } 
  else { 
    console. log( data ); 
  } 
} 
ajax( "http:// some. url. 1", response );

這兩種方式解決了一些問題,減小了一些工做量, 可是依然沒有完全解決問題。首先它們的可複用性依然不強,其次,如回調被屢次調用的問題依然沒法解決。

Promise如何解決這兩個問題

Promise已是原生支持的API了,它已經被加到了JS的規範裏面,在各大瀏覽器中的運行機制是相同的。這樣就保證了它的可靠。

如何解決可讀性的問題

這一點不用多說,用過Promise的人很容易明白。Promise的應用至關於給了你一張能夠把解題思路清晰記錄下來的草稿紙,你不在須要用腦子去記憶執行順序。

如何解決信任問題

Promise並無取消控制反轉,而是把反轉出去的控制再反轉一次,也就是反轉了控制反轉。

這種機制有點像事件的觸發。它與普通的回調的方式的區別在於,普通的方式,回調成功以後的操做直接寫在了回調函數裏面,而這些操做的調用由第三方控制。在Promise的方式中,回調只負責成功以後的通知,而回調成功以後的操做放在了then的回調裏面,由Promise精確控制。

Promise有這些特徵:只能決議一次,決議值只能有一個,決議以後沒法改變。任何then中的回調也只會被調用一次。Promise的特徵保證了Promise能夠解決信任問題。

對於回調過早的問題,因爲Promise只能是異步的,因此不會出現異步的同步調用。即使是在決議以前的錯誤,也是異步的,並非會產生同步(調用過早)的困擾。

var a = new Promise((resolve, reject) => {
  var b = 1 + c;  // ReferenceError: c is not defined,錯誤會在下面的a打印出來以後報出。
  resolve(true);
})
console.log(1, a);
a.then(res => {
  console.log(2, res);
})
.catch(err => {
  console.log(err);
})

對於回調過晚或沒有調用的問題,Promise自己不會回調過晚,只要決議了,它就會按照規定運行。至於服務器或者網絡的問題,並非Promise能解決的,通常這種狀況會使用Promise的競態APIPromise.race加一個超時的時間:

function timeoutPromise(delay) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject("Timeout!");
    }, delay);
  });
}

Promise.race([doSomething(), timeoutPromise(3000)])
.then(...)
.catch(...);

對於回調次數太少或太多的問題,因爲Promise只能被決議一次,且決議以後沒法改變,因此,即使是屢次回調,也不會影響結果,決議以後的調用都會被忽略。

參考資料:

相關文章
相關標籤/搜索