翻譯:We have a problem with promises

原文:https://pouchdb.com/2015/05/1...javascript

JavaScripts的朋友們,是時候認可了: we have a problem with promises。不,不是promises自己。正如A+ spec所定義的,promises是很是棒的。html

在過去的一年裏,當我看到許多程序員在PouchDB API和其餘promise-heavy APIs上掙扎時,我發現了一個大問題:咱們中的許多人使用promises 時沒有真正理解它們java

若是你以爲很難相信,想一想我最近在Twitter上發佈的這個謎題:node

Q: What is the difference between these four promises?git

doSomething().then(function () {
  return doSomethingElse();
});

doSomething().then(function () {
  doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);


若是你知道答案,那麼恭喜你:你是一個承諾忍者。我容許您中止閱讀此日誌。對於其餘99.99%的人來講,你是一個很好的同伴。沒有人迴應個人推特,也沒有人能解決這個問題,我本身對#3的答案感到驚訝。是的,即便我寫了測驗!

答案在這篇文章的最後,但首先,我想探討一下爲何promises一開始就那麼棘手,爲何咱們中的許多人——新手和專家——會被promises絆倒。我還將提供我認爲是獨特看法的東西,一個奇異的把戲,它使promises很容易理解。是的,我真的相信在那以後他們不會那麼難!

但首先,讓咱們挑戰一些關於promises的常見假設。

Wherefore promises?

若是你讀過有關promises的文獻,你會常常發現對the pyramid of doom(https://medium.com/@wavded/managing-node-js-callback-hell-1fe03ba8baf)的引用,其中有一些可怕的callback-y代碼穩步地向屏幕的右側延伸。

promises確實解決了這個問題,但它不只僅是縮進。正如"Redemption from Callback Hell"(http://youtu.be/hf1T_AONQJU)中所解釋的,callbacks的真正問題是它們剝奪了咱們return和throw這樣的關鍵字。相反,咱們的程序的整個流程基於side effects:一個函數偶然調用另外一個函數。

事實上,callbacks 作了一些更險惡的事情:它們剝奪了咱們的stack, stack在編程語言中咱們一般認爲是理所固然的。寫沒有stack的代碼很像駕駛一輛沒有剎車踏板的汽車:你不會意識到你有多麼須要它,直到你伸手去拿它而它不在那裏。

promises的所有要點是就是把異步時丟失的語言基礎還給咱們:return, throw, 和 stack。可是你必須知道如何正確地使用promises,才能利用它們。

Rookie mistakes

有些人試圖把承諾解釋成cartoon(https://www.andyshora.com/promises-angularjs-explained-as-cartoon.html),或者以一種很是面向名詞的方式:「哦,正是你能夠傳遞的東西表明了一個異步值。」

我以爲這樣的解釋沒什麼幫助。對我來講,promises都是關於代碼結構和流程的。因此我認爲最好是回顧一些常見的錯誤,並展現如何修復它們。我把這些叫作"rookie mistakes",意思是,「你如今是新手了,孩子,但你很快就會成爲職業選手。」

Quick digression::「promises」對不一樣的人來講意味着不少不一樣的事情,可是在本文中,我將只討論官方規範(https://promisesaplus.com/),就像window.Promise在現代瀏覽器中同樣。並非全部的瀏覽器都有window.Promise,所以,要想獲得一個好的polyfill,請看一個名爲Lie(https://github.com/calvinmetcalf/lie)的庫,它是目前最小的符合規範的庫。

Rookie mistake #1: the promisey pyramid of doom

看看人們是如何使用PouchDB的,PouchDB有一個很大程度上基於promise的API,我發現不少糟糕的promise模式。最多見的糟糕的作法是:

remotedb.allDocs({
  include_docs: true,
  attachments: true
}).then(function (result) {
  var docs = result.rows;
  docs.forEach(function(element) {
    localdb.put(element.doc).then(function(response) {
      alert("Pulled doc with id " + element.doc._id + " and added to local db.");
    }).catch(function (err) {
      if (err.name == 'conflict') {
        localdb.get(element.doc._id).then(function (resp) {
          localdb.remove(resp._id, resp._rev).then(function (resp) {
// et cetera...

是的,事實證實你能夠像回調同樣使用promises ,是的,這很像用電動砂光機銼指甲,但你能夠作到。

若是你認爲這類錯誤僅僅侷限於絕對初學者,你會驚訝地發現我確實從官方的黑莓開發者博客中獲取了上述代碼!舊的回調習慣很難改變。(對開發人員說:很抱歉挑你的毛病,但你的例子頗有啓發性。)

A better style is this one:

remotedb.allDocs(...).then(function (resultOfAllDocs) {
  return localdb.put(...);
}).then(function (resultOfPut) {
  return localdb.get(...);
}).then(function (resultOfGet) {
  return localdb.put(...);
}).catch(function (err) {
  console.log(err);
});
這被稱爲composing promises,它是promises的great superpowers之一。每一個函數只有在上一個Promise resolved後纔會被調用,而且將使用該Promise的輸出來調用它。更多的內容之後再談。

Rookie mistake #2: WTF, how do I use forEach() with promises?

這就是大多數人對承諾的理解開始崩潰的地方。一旦他們到了熟悉的foreach()循環(或者for循環,或者while循環),他們就不知道如何讓它與promises一塊兒工做。因此他們寫了這樣的東西:
    // I want to remove() all docs
    db.allDocs({include_docs: true}).then(function (result) {
      result.rows.forEach(function (row) {
        db.remove(row.doc);  
      });
    }).then(function () {
      // I naively believe all docs have been removed() now!
    });
這個代碼有什麼問題?問題是第一個函數實際上返回undefined,這意味着第二個函數不等待對全部文檔調用db.remove()。實際上,它不須要等待任何東西,而且能夠在刪除任意數量的文檔後執行!
這是一個特別陰險的bug,由於您可能不會注意到任何錯誤,假設PouchDB刪除這些文檔的速度足以更新您的UI。這個bug可能只在odd race條件下出現,或者在某些瀏覽器中出現,此時幾乎不可能進行調試。
全部這些的TLDR 都是forEach()/for/while 不是您要查找的構造。你須要Promise.all():
    db.allDocs({include_docs: true}).then(function (result) {
      return Promise.all(result.rows.map(function (row) {
        return db.remove(row.doc);
      }));
    }).then(function (arrayOfResults) {
      // All docs have really been removed() now!
    });

這是怎麼回事?基本上Promise.all() 接受一個array of promises做爲輸入,而後它給您另外一個promise,該promise只在其餘全部的promise都resolved時纔會解決。它是for循環的異步等價物。

Promise.all() 還將一個結果數組傳遞給下一個函數,這很是有用,例如,若是您試圖從pouchdb去get()多個結果。若是它的任何一個sub-promises are rejected,那麼all()承諾也會被拒絕,這更有用。

Rookie mistake #3: forgetting to add .catch()

這是另外一個常見的錯誤。幸運的是,他們的promises永遠不會拋出錯誤,許多開發人員忘記在代碼中的全部地方添加.catch()。不幸的是,這意味着任何拋出的錯誤都將被吞沒,您甚至不會在控制檯中看到它們。這多是調試真正的苦惱。
爲了不這種糟糕的狀況,我養成了在個人promise chains中添加如下代碼的習慣:
    somePromise().then(function () {
      return anotherPromise();
    }).then(function () {
      return yetAnotherPromise();
    }).catch(console.log.bind(console)); // <-- this is badass
即便您不指望出現錯誤,也要謹慎地添加catch()。若是你的假設被證實是錯誤的,這會讓你的生活更輕鬆。

Rookie mistake #4: using "deferred"

這是一個錯誤 我看all the time,我甚至不肯意在這裏重複它,由於我擔憂,像甲蟲汁同樣,僅僅調用它的名字就會引起更多的例子。簡言之,promises 有着悠久的歷史,而JavaScript社區花了很長時間才使其正確。早期,jQuery 和Angular在各地都使用這種「deferred」模式,如今已經被ES6 Promise規範所取代,由「good」庫(如Q, When, RSVP, Bluebird, Lie, and others庫)實現。

因此若是你在代碼中寫這個詞(我不會第三次重複!)你作錯了一些事。下面是如何避免它。
首先,大多數承諾庫都爲您提供了從第三方庫「import」promises 的方法。例如,Angular的$q模塊容許您使用$q.when()包裝non-$q承諾。因此Angular用戶能夠這樣包裝PouchDB承諾:
    $q.when(db.put(doc)).then(/* ... */); // <-- this is all the code you need
另外一種策略是使用revealing constructor pattern(https://blog.domenic.me/the-revealing-constructor-pattern/),這對於包裝 non-promise的API頗有用。例如,要包裝基於回調的API,如Node的fs.readfile(),只需執行如下操做:
    new Promise(function (resolve, reject) {
      fs.readFile('myfile.txt', function (err, file) {
        if (err) {
          return reject(err);
        }
        resolve(file);
      });
    }).then(/* ... */)
Done! We have defeated the dreaded def... Aha, caught myself. :)
有關爲何這是anti-pattern的更多信息,請訪問Bluebird wiki上的Promise anti-patterns頁面(https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern)。

Rookie mistake #5: using side effects instead of returning

這個代碼怎麼了?
    somePromise().then(function () {
      someOtherPromise();
    }).then(function () {
      // Gee, I hope someOtherPromise() has resolved!
      // Spoiler alert: it hasn't.
    });

好吧,這是一個很好的觀點,能夠談論關於promises的全部你須要知道的事情。說真的,這是一個one weird trick,一旦你理解了它,就會阻止我所說的全部錯誤。準備好了嗎?
正如我以前所說,promises 的魔力在於,它們把咱們寶貴的return 和 throw還給咱們。但在實踐中這究竟是什麼樣子的呢?
每個承諾都會給你一個then()方法(或catch(),它只是then(null, ...)的語法糖)。這裏是then()函數的內部:
    somePromise().then(function () {
      // I'm inside a then() function!
    });
咱們在這裏能作什麼?有三件事:
    1. return another promise
    2. return a synchronous value (or undefined)
    3. throw a synchronous error
就這樣。一旦你理解了這個訣竅,你就明白了promises。因此,So let's go through each point one at a time.。

1. Return another promise
    這是您在promise文獻中看到的常見模式,如上面的「composing promises」示例所示:
        getUserByName('nolan').then(function (user) {
          return getUserAccountById(user.id);
        }).then(function (userAccount) {
          // I got a user account!
        });
    請注意,我正在返回第二個promise—return是相當重要的。若是我沒有說return,那麼getUserAccountByID()其實是一個side effect,下一個函數將接收undefined而不是userAccount。
2. Return a synchronous value (or undefined)

返回undefined一般是一個錯誤,但返回同步值其實是將同步代碼轉換爲Promisey代碼的一種很棒的方法。例如,假設咱們有一個用戶的內存緩存。咱們能夠作到:
    getUserByName('nolan').then(function (user) {
      if (inMemoryCache[user.id]) {
        return inMemoryCache[user.id];    // returning a synchronous value!
      }
      return getUserAccountById(user.id); // returning a promise!
    }).then(function (userAccount) {
      // I got a user account!
    });

那不是太棒了嗎?第二個函數不關心是同步仍是異步獲取用戶賬戶,第一個函數能夠自由返回同步或異步值。
不幸的是,在JavaScript中,non-returning函數在技術上返回undefined結果是不方便的,這意味着當您打算返回某些內容時,很容易意外地引入side effects 。出於這個緣由,我習慣於老是從then()函數內部返回或拋出。我建議你也這麼作。
  1. Throw a synchronous error
    說到throw,這就是promises能夠變得使人驚歎的地方。假設咱們想要拋出一個同步錯誤,以防用戶註銷。這很容易:程序員

    getUserByName('nolan').then(function (user) {
      if (user.isLoggedOut()) {
        throw new Error('user logged out!'); // throwing a synchronous error!
      }
      if (inMemoryCache[user.id]) {
        return inMemoryCache[user.id];       // returning a synchronous value!
      }
      return getUserAccountById(user.id);    // returning a promise!
    }).then(function (userAccount) {
      // I got a user account!
    }).catch(function (err) {
      // Boo, I got an error!
    });

    若是用戶註銷,咱們的catch()將收到一個同步錯誤;若是任何promises被拒絕,它將收到一個異步錯誤。一樣,函數不關心它獲得的錯誤是同步的仍是異步的。這尤爲有用,由於它能夠幫助識別開發過程當中的編碼錯誤。例如,若是在then()函數內的任何一點執行json.parse(),那麼若是json無效,它可能會拋出一個同步錯誤。經過callbacks,這個錯誤會被忽略,可是經過promise,咱們能夠在catch() 函數中簡單地處理它。angularjs

Advanced mistakes

好吧,既然你已經學會了一個讓promises變得簡單的訣竅,咱們來談談邊緣案例。由於固然,老是有邊緣狀況。
我將這些錯誤歸類爲「高級錯誤」,由於我只在那些已經至關擅長promises的程序員所犯的錯誤中見過。可是若是咱們想解決我在本文開頭提出的難題的話.咱們須要討論一下。

Advanced mistake #1: not knowing about Promise.resolve()

正如我上面所展現的,promises 對於將同步代碼包裝爲異步代碼很是有用。可是,若是你發現本身常常輸入:
    new Promise(function (resolve, reject) {
      resolve(someSynchronousValue);
    }).then(/* ... */);
您可使用promise.resolve()更簡潔地表達這一點:
    Promise.resolve(someSynchronousValue).then(/* ... */);
這對於捕獲任何同步錯誤也很是有用。它是如此有用,以致於我養成了一個習慣,幾乎我全部的 promise-returning API方法都是這樣的:
    function somePromiseAPI() {
      return Promise.resolve().then(function () {
        doSomethingThatMayThrow();
        return 'foo';
      }).then(/* ... */);
    }

只需記住:任何可能同步拋出的代碼都是一個很好的candidate,由於它幾乎不可能在一行中的某個地方調試吞沒的錯誤。可是,若是您將全部內容都包裝在promise.resolve()中,那麼您之後老是能夠確保catch() 。
一樣,您可使用promise.reject()返回一個當即被拒絕的承諾:
    Promise.reject(new Error('some awful error'));

Advanced mistake #2: then(resolveHandler).catch(rejectHandler) isn't exactly the same as then(resolveHandler, rejectHandler)

我在上面說,catch()只是語法糖。因此這兩個片斷是等效的:
    somePromise().catch(function (err) {
      // handle error
    });
    
    somePromise().then(null, function (err) {
      // handle error
    });
可是,這並不意味着如下兩個片斷是等效的:
    somePromise().then(function () {
      return someOtherPromise();
    }).catch(function (err) {
      // handle error
    });
    
    somePromise().then(function () {
      return someOtherPromise();
    }, function (err) {
      // handle error
    });
若是您想知道爲何它們不是等價的,那麼考慮一下若是第一個函數拋出一個錯誤會發生什麼:
    somePromise().then(function () {
      throw new Error('oh noes');
    }).catch(function (err) {
      // I caught your error! :)
    });
    
    somePromise().then(function () {
      throw new Error('oh noes');
    }, function (err) {
      // I didn't catch your error! :(
    });
事實證實,當使用then(resolveHandler, rejectHandler)格式時,若是resolveHandler自己拋出了錯誤,那麼rejecthandler實際上不會捕獲錯誤。出於這個緣由,我已經習慣了永遠不要使用then()的第二個參數,而且老是更喜歡catch()。例外狀況是,當我在編寫異步mocha測試時,我可能會編寫一個測試來確保拋出一個錯誤:
    it('should throw an error', function () {
      return doSomethingThatThrows().then(function () {
        throw new Error('I expected an error!');
      }, function (err) {
        should.exist(err);
      });
    });
說到這一點,Mocha和Chai是測試Promise API的可愛組合。pouchdb-plugin-seed項目有一些示例測試可讓您開始。

Advanced mistake #3: promises vs promise factories

假設你想一個接一個地按順序執行一系列的promises 。也就是說,您須要像promise.all()這樣的東西,但它不能並行地執行promises。
你可能天真地寫了這樣的東西:
    function executeSequentially(promises) {
      var result = Promise.resolve();
      promises.forEach(function (promise) {
        result = result.then(promise);
      });
      return result;
    }

不幸的是,這不會像你想的那樣奏效。您傳遞給executeSequentially()的promises 仍將並行執行。發生這種狀況的緣由是,你根本不想對一系列承諾進行操做。根據Promise規範,一旦建立了promise,它就開始執行。因此你真正想要的是一系列的promise factories:
    function executeSequentially(promiseFactories) {
      var result = Promise.resolve();
      promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
      });
      return result;
    }
我知道你在想:「這個Java程序員究竟是誰,爲何他要談論factories?」然而,Promise factories很是簡單——它只是一個返回Promise的函數:
    function myPromiseFactory() {
      return somethingThatCreatesAPromise();
    }
爲何會這樣?它起做用是由於promise factory在被要求以前不會創造promise。它與then函數的工做方式相同——事實上,它是相同的!
若是你看上面的executeSequentially() 函數,而後想象myPromiseFactory在result.then(...)中被替換了,那麼但願一個燈泡會在你的大腦中發出咔嗒聲。在那一刻,你將得到promise啓發。

Advanced mistake #4: okay, what if I want the result of two promises?

一般狀況下,一個promise 依賴於另外一個promise ,但咱們須要兩個promises的輸出。例如: 
    getUserByName('nolan').then(function (user) {
      return getUserAccountById(user.id);
    }).then(function (userAccount) {
      // dangit, I need the "user" object too!
    });
爲了成爲優秀的javascript開發人員並避免pyramid of doom,咱們可能只將用戶對象存儲在一個更高範圍的變量中:
    var user;
    getUserByName('nolan').then(function (result) {
      user = result;
      return getUserAccountById(user.id);
    }).then(function (userAccount) {
      // okay, I have both the "user" and the "userAccount"
    });
這是可行的,但我我的以爲有點笨拙。我建議的策略是:拋開你的先入之見,擁抱pyramid:
    getUserByName('nolan').then(function (user) {
      return getUserAccountById(user.id).then(function (userAccount) {
        // okay, I have both the "user" and the "userAccount"
      });
    });
…至少是暫時的。若是縮進變成了一個問題,那麼您能夠按照Javascript開發人員自古以來的作法,將函數提取到一個命名函數中:
    function onGetUserAndUserAccount(user, userAccount) {
      return doSomething(user, userAccount);
    }
    
    function onGetUser(user) {
      return getUserAccountById(user.id).then(function (userAccount) {
        return onGetUserAndUserAccount(user, userAccount);
      });
    }
    
    getUserByName('nolan')
      .then(onGetUser)
      .then(function () {
      // at this point, doSomething() is done, and we are back to indentation 0
    });
隨着您的promise代碼變得愈來愈複雜,您可能會發現本身正在將愈來愈多的函數提取到命名函數中。我發現這會產生很是美觀的代碼,看起來像這樣:
putYourRightFootIn()
  .then(putYourRightFootOut)
  .then(putYourRightFootIn)  
  .then(shakeItAllAbout);
這就是promises的意義所在。

Advanced mistake #5: promises fall through

最後,這是我在介紹上述promise puzzle時提到的錯誤。這是一個很是深奧的用例,它可能永遠不會出如今您的代碼中,但它確實讓我吃驚。
你以爲這個代碼能打印出來嗎?
    Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
      console.log(result);
    });
若是你認爲它打印出了bar,你就錯了。它實際上打印了foo!
發生這種狀況的緣由是,當您傳遞then()一個non-function (如promise)時,它實際上將其解釋爲then(null),這會致使前一個promise的結果失敗。您能夠本身測試:
    Promise.resolve('foo').then(null).then(function (result) {
        console.log(result);
    });

添加任意then(null)s;它仍將打印foo。
這實際上回到了我以前關於promises和promise factories的觀點。簡而言之,您能夠將一個promise直接傳遞到then()方法中,但它不會執行您認爲它正在執行的操做。then()應該接受一個函數,因此最有可能的狀況是:
    Promise.resolve('foo').then(function () {
      return Promise.resolve('bar');
    }).then(function (result) {
      console.log(result);
    });
如咱們所料,這將打印bar。
因此請提醒本身:老是向then()傳遞函數!

Solving the puzzle

既然咱們已經瞭解了關於promises 的一切(或接近promises 的一切!)咱們應該可以解決我最初在這篇文章開頭提出的難題。如下是每一個問題的答案,採用圖形格式,以便更好地可視化:

Puzzle #1
    doSomething().then(function () {
      return doSomethingElse();
    }).then(finalHandler);
Answer:
    doSomething
    |-----------------|
                      doSomethingElse(undefined)
                      |------------------|
                                         finalHandler(resultOfDoSomethingElse)
                                         |------------------|
Puzzle #2
    doSomething().then(function () {
      doSomethingElse();
    }).then(finalHandler);
Answer:
    doSomething
    |-----------------|
                      doSomethingElse(undefined)
                      |------------------|
                      finalHandler(undefined)
                      |------------------|
Puzzle #3
    doSomething().then(doSomethingElse())
      .then(finalHandler);
Answer:
    doSomething
    |-----------------|
    doSomethingElse(undefined)
    |---------------------------------|
                      finalHandler(resultOfDoSomething)
                      |------------------|
Puzzle #4
    doSomething().then(doSomethingElse)
      .then(finalHandler);
Answer:
    doSomething
    |-----------------|
                      doSomethingElse(resultOfDoSomething)
                      |------------------|
                                         finalHandler(resultOfDoSomethingElse)
                                         |------------------|

    若是這些答案仍然沒有意義,那麼我建議您從新閱讀文章,或者定義dosomething()和dosomethingelse()方法,並在瀏覽器中本身嘗試。

    Clarification:對於這些示例,我假設doSomething()和doSomethingElse()都返回promises,而且這些promises表示在javascript事件循環以外所作的事情(例如IndexedDB, network, setTimeout),這就是爲何它們在適當的時候顯示爲併發的緣由。這裏有一個JSbin要演示。

爲了更高級地使用promises,請查看個人承promise protips cheat sheet(https://gist.github.com/nolanlawson/6ce81186421d2fa109a4)。

Final word about promises

Promises是偉大的。若是你仍在使用callbacks,我強烈建議你轉用promises。您的代碼將變得更小、更優雅、更容易理解。若是你不相信我,這裏有一個證據:a refactor of PouchDB's map/reduce module (https://t.co/hRyc6ENYGC),用promises替換callbacks。結果:290次插入,555次刪除。
順便說一下,寫那個討厭的回調代碼的人是……我!所以,這是我在promises的原始力量方面的第一堂課,我感謝其餘PouchDB貢獻者在這一過程當中對個人指導。
儘管如此,promises不完美。的確,他們比回調更好,但這很像是說,一拳打在肚子上總比一拳打在牙齒上好。固然,一個比另外一個更好,可是若是你有選擇的話,你可能會避開它們。

雖然優於callbacks,promises仍然很難理解和容易出錯,這一點能夠證實,我以爲有必要寫這篇博文。新手和專家都會常常把事情搞得一團糟,事實上,這不是他們的錯。問題是,雖然與咱們在同步代碼中使用的模式相似,但承諾是一個不錯的替代品,但並不徹底相同。事實上,您沒必要學習一堆神祕的規則和新的API來作一些事情,在同步的世界中,您能夠很好地處理熟悉的模式,如 return, catch, throw, and for-loops。不該該有兩個平行的系統,這個系統是你必須一直保持頭腦中的直線。

Awaiting async/await

這就是我在 "Taming the asynchronous beast with ES7"(https://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html),中提出的觀點,在這裏我研究了ES7 async/await關鍵字,以及它們如何將承諾更深刻地集成到語言中。ES7沒必要編寫僞同步代碼(使用一個相似catch的fake catch()方法,但實際上不是),它容許咱們使用真正的try/catch/return關鍵字,就像咱們在CS 101中學習到的那樣。
這對JavaScript做爲一種語言來講是一個巨大的好處。由於最終,只要咱們的工具不告訴咱們何時出錯,這些promise anti-patterns仍然會不斷出現。
以javascript的歷史爲例,我認爲能夠公平地說,JSlint和JShint爲社區提供了比JavaScript: The Good Parts更好的服務,即便它們實際上包含相同的信息。二者的區別是:告知你在代碼中犯的錯誤,而不是讀一本你試圖理解別人錯誤的書。

ES7 Async/Await的優勢是,在大多數狀況下,您的錯誤將顯示爲語法/編譯器錯誤,而不是細微的運行時錯誤。不過,在那以前,最好掌握promises的能力,以及如何在ES5和ES6中正確地使用它們。
因此,雖然我認識到,像JavaScript: The Good Parts,這個博客文章只能產生有限的影響,但但願你能在看到人們犯一樣的錯誤時指出這些問題。由於咱們中仍有太多人須要認可:"I have a problem with promises!"
Update:有人告訴我,Bluebird3.0會打印出警告,能夠防止我在這篇文章中發現的許多錯誤。因此當咱們等待ES7時,使用Bluebird是另外一個很好的選擇!
相關文章
相關標籤/搜索