關於異步Promises

英文原文:What's The Point Of Promises?
  • 迄今爲止,可能每一個JavaScript開發者和他們的祖母都據說過Promises。若是你沒有,那麼你即將會。promises的概念是由CommonJS小組的成員在 Promises/A規範 中提出來的。Promises被逐漸用做一種管理異步操做回調的方法,但出於它們的設計,它們遠比那個有用得多。事實上,因爲它們的多種用法,有無數人告訴我——在我寫過一些關於promises的東西后——我「遺漏了promises的重點」。那麼什麼是promises的重點呢?

 

一點關於Promises的東西

  • 在我開始promise的「重點」以前,我想我應該給你一點它們如何工做的內貌。一個promise是一個對象——根據Promise/A規範——只須要一個方法:then。then方法帶有三個參數:一個成功回調,一個失敗回調,和一個前進回調(規範沒有要求包括前進回調的實現,可是不少都實現了)。一個全新的promise對象從每一個then的調用中返回。
  • 一個promise能夠是三種狀態之一:未完成的,完成的,或者失敗的。promise以未完成的狀態開始,若是成功它將會是完成態,若是失敗將會是失敗態。當一個promise移動到完成態,全部註冊到它的成功回調將被調用,並且會將成功的結果值傳給它。另外,任何註冊到promise的成功回調,將會在它已經完成之後當即被調用。

  • 一樣的事情發生在promise移動到失敗態的時候,除了它調用的是失敗回調而不是成功回調。對包含前進特性的實現來講,promise在它離開未完成狀態之前的任什麼時候刻,均可以更新它的progress。當progress被更新,全部的前進回調(progress callbacks)會被傳遞以progress的值,並被當即調用。前進回調被以不一樣於成功和失敗回調的方式處理;若是你在一個progress更新已經發生之後註冊了一個前進回調,新的前進回調只會在它被註冊之後被已更新的progress調用。
  • 咱們不會進一步深刻promise狀態是如何管理的,由於那不在規範以內,並且每一個實現都有差異。在後面的例子中,你將會看到它是如何完成的,但目前這就是全部你須要知道的。

 

處理回調

像前面提到的爲異步操做處理回調,是promises的最基本和最普通的用途,讓咱們將一個標準的回調與一個採用了promise的回調比較一下。javascript

 

// Normal callback usage
asyncOperation(function() {
    // Here's your callback
});

// Now `asyncOperation` returns a promise
asyncOperation().then(function(){
    // Here's your callback
});

我很懷疑只是看到這個例子的話是否有人會真的關心去使用promises。看起來沒有什麼好處,除了「then」使得在異步操做完成以後的回調函數被調用這件事看起來更加明顯。可是即便是這個好處,咱們如今有了更多的代碼(抽象應該使咱們的代碼更短,不是嗎?)並且promise比標準回調稍微性能差一點。java

可是,不要讓這阻礙到你。若是這就是promise能夠作的最好的事,這篇文章就不會存在了jquery

 

 

厄運的金字塔

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(function(data){
    // Do some processing with `data`
    anotherAsync(function(data2){
        // Some more processing with `data2`
        yetAnotherAsync(function(){
            // Yay we're finished!
        });
    });
});

// Let's look at using promises
asyncOperation()
.then(function(data){
    // Do some processing with `data`
    return anotherAsync();
})
.then(function(data2){
    // Some more processing with `data2`
    return yetAnotherAsync();
})
.then(function(){
    // Yay we're finished!
});

正如你所見,promises的使用使得事情變扁平並且更可讀了。這能起做用是由於——像早先提到的——then返回了一個promise,因此你能夠將then的調用不停的串連起來。由then返回的promise裝載了由調用返回的值。若是調用返回了一個promise(像這個例子中的情形同樣),then返回的 promise裝載了與你的回調返回的promise所裝載的相同值。這內容不少,所以我將幫助你一步一步的理解它git


異步操做返回一個promise對象。所以咱們在那個promise對象中調用then,而且傳給它一個回調函數;then也會返回一個promise。當異步操做結束,它將給promise裝上數據。而後(第一次)回調被調用,數據被做爲參數傳遞進去。若是回調不含有返回值,then返回的promise將會當即不帶值組裝。若是回調返回的不是一個promise,那麼then返回的 promise將會當即裝載那個數值。若是回調返回一個promise(像例子中的),那麼then返回的 promise將等待直到咱們回調返回的promise被徹底裝載。一旦咱們回調的 promise被裝載,它裝載的值(本例中就是data2)將會被提交給then的promise。而後then中的promise裝載了data2。等等。聽起來有點複雜,但事實上很簡單,若是我說的你不能理解,我很是抱歉。我猜我可能不是談論它的最佳人選。github

 

 

用命名的回調替代

但顯然 promises 不是使這個結構扁平化的惟一方法。在寫了一篇提到promises解決了厄運的金字塔問題的帖子以後,有我的對該帖評論說……web

我想promises有時是有用的,可是「嵌套」的回調的問題(聖誕樹綜合症)能夠僅用一個命名的函數做爲一個參數替代匿名函數的方法日常的處理:

asyncCall( param1, param2, HandlerCallback );

function HandlerCallback(err, res){
// do stuff
}

它的例子只是給出了一層深的例子,但它還是正確的。咱們來擴展我前面的例子,使這個看起來容易些。數據庫

 

命名回調

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(handler1);

function handler1(data) {
    // Do some processing with `data`
    anotherAsync(handler2);
}

function handler2(data2) {
    // Some more processing with `data2`
    yetAnotherAsync(handler3);
}

function handler3() {
    // Yay we're finished!
}

看看上面的代碼!他們絕對是對的!它就是一個扁平的結構,可是這裏有個問題一樣也存在於 我之前歷來沒有注意過的老的回調例子中:依賴性和複用性。依賴性和複用性是相互關聯的可逆類型。同樣東西依賴的越少,那麼它的複用性就越大。在以上的例子中,handler1依賴handler2,handler2依賴handler3.這就意味着handler1不管出於任何目的都不可在被用除非handler2也呈現出來。假如你不打算重用他們,那麼給你的函數命名又有什麼意義呢?編程

 

最糟糕的的是handler1都不關心在handler2裏面發生了什麼事情。它壓根就不須要handler2除了和它異步工做。所以,讓咱們消除這些依賴性而後經過用promise使函數更具複用性。api

 

 

鏈式回調

asyncOperation().then(handler1).then(handler2).then(handler3);

function handler1(data) {
    // Do some processing with `data`
    return anotherAsync();
}

function handler2(data2) {
    // Some more processing with `data2`
    return yetAnotherAsync();
}

function handler3() {
    // Yay we're finished!
}

這樣看起來是否是好多了?假如另外的函數存在的話,如今handler1和handler2都互不相關了。想看看他們是否真的很棒呢?如今handler1能夠被用在不須要handler2的狀況下了。相反,handler1被操做之後,咱們將能夠用另外一個handler。數組

 

 

複用函數

asyncOperation().then(handler1).then(anotherHandler);

function handler1(data) {
    // Do some processing with `data`
    return anotherAsync();
}

function anotherHandler(data2) {
    // Do some really awesome stuff that you've never seen before. It'll impress you
}

如今handler1已經從handler2脫離並且能夠被用在了更多的情形中,特別是那些由handler2提供的功能而咱們又不想用的。這就是複用性!評論家解決代碼易讀性的惟一方法就是經過消除縮進。咱們不想消除縮進僅僅是爲了縮進。多層次的縮進僅僅是某些事情錯誤的標誌,問題不必定在它自己。他就像是由脫水引發的頭痛。真正的問題是脫水,不是頭痛。解決的方法是得到水合物,而不是用一些止痛藥。

 

 

並行異步操做

在前面我提到的文章裏,我將promises與events在處理異步操做方面作了比較。遺憾的是,按照那些曾提到過的人在評論裏給的說法,我比較的不是很成功。我描述出了promises的力量,接着轉到events來描述它們的力量,就像在個人特別項目裏用到的那樣。沒有比較和對比。一位評論者寫道(修改了一點語法錯誤):

我想用帖子中的例子是一個壞的對照。有篇論文證實了promises的值將會怎樣,若是按下虛構的「啓動服務器按鈕」,將不只僅是啓動一個web服務器,還有一個數據庫服務器,當它們都在運行的時候只是更新了UI。

使用promise的.when方法將會使這種「多個異步操做」例子變得普通,然而響應多個異步事件須要一個並不普通的代碼量。

他徹底正確。事實上我沒有比較那兩種狀況。那篇文章的要點實際在於說明promises不是異步操做的惟一機制,並且在一些狀況下,它們也不必定是最好的。在這個評論者指出的狀況下,promises固然是最佳的解決辦法。咱們來看看他說的是什麼

 


 

jQuery 具備 一個名爲when的方法 ,能夠帶上任意數量的promise參數,並返回一個單一的promise。若是任何一個promise傳入失敗,when返回的promise也會失敗。若是全部的promises被裝載,那麼每一個值都將會按照promises被定義的順序傳遞給附加的回調。

以並行的方式執行無數的異步操做很是有用,而後只要在它們之中的每個結束以後繼續執行回調。咱們看一個簡單的例子。

 

jQuery.when
// Each of these async functions return a promise
var promise1 = asyncOperation1();
var promise2 = asyncOperation2();
var promise3 = asyncOperation3();

// The $ refers to jQuery
$.when(promise1, promise2, promise3).then(
    function(value1, value2, value3){
        // Do something with each of the returned values
    }
);

人們常常說這是 promises 帶來的最好的東西之一,也是 promises 的一部分重要的意義所在。我也認爲這是個簡化了大量操做的好特性,可是這種 when 方法的機制 根本就沒有在任何 Promises 規範中提到,因此我不認爲它是 Promises意義所在。有一個規範提到了 when 方法,可是和上面的徹底不一樣。就我所知,jQuery 是惟一的實現了這種 when 方法的庫。其餘的 promises 庫,例如  Q, Dojo, 和  when 依照  Promises/B spec 實現了 when 方法, 可是並無實現註釋者說起的 when 方法。可是,Q 庫有一個   all方法,when.js 也有一個  parallel方法,與上面的 jQuery.when 方法做用同樣,只是它們接受一個數組類型的參數,而不是任意數量的參數。

 

 

值的表示

Promise是處理如下場景的更好的方法:

"我想在這個數據庫中找一個用戶,但find方法是異步的。"

所以,這裏咱們有了一個不能馬上返回值的find方法。但最終它確實"返回"了一個數值(經過一個回調的方式),而你但願以某種方式處理那個數值。如今,經過使用一個回調,你能定義一個繼續部分,或者說「一些將在之後時間裏處理那個數值的代碼」

Promise改變了那種「嘿,這裏是一些你會發現你用來處理返回數值的代碼」。它們是一些容許"find"方法說「嘿,我將忙着找你要找的信息,但與此同時你能繼續等着返回結果,並且你能同時以任何你但願的方式處理它,就像實際的東西!」

Promise表明了真實的數值。那就是陷阱。它們工做在你像處理實際東西同樣處理Promise的時候。Promise的JavaScript實現期待你給它傳遞一個回調函數,這只是一個「巧合」,它不是重要的事情。

我相信這真的就是promise的重點。爲何?讀一讀 Promise/A規範 的第一句「一個promise表明了一個操做的一次完成最終返回的數值。「使它有點明顯了,是否是?好吧,即便那就是重點,那也不能阻止我在後面本文中呈現其餘人的看法。無論怎麼說,咱們再多談論這個思想一點。

 

結論

    promise的重點是它表明一個操做返回的最終結果值,但使用它們的緣由是使同步操做更好的並行。自從異步編程進入此場景,處處都是彈出的回調,以奇怪的方式遮住咱們的代碼。Promise是一種改變其的方法。Promise容許咱們以同步的方式寫代碼,同時給予咱們代碼的異步執行。

相關文章
相關標籤/搜索