用Javascript的小夥伴們,是時候認可了,關於 promises 咱們一直存在着問題。並不是說 promises 自己有問題,Promises/A+ 是極好的。html
就我過去數年觀察大量 PouchDB API 以及其餘 promise-heavy API 的使用者們與這些 API 的搏鬥中我發現,最大的問題是:git
大部分使用 promises 的小夥伴們並無真正的理解它web
若是你不認同這個觀點,能夠看看我最近在 twitter 上出的這道題:編程
Q: 下面的四種 promises 的區別是什麼windows
doSomething().then(function () { return doSomethingElse(); }); doSomething().then(function () { doSomethingElse(); }); doSomething().then(doSomethingElse()); doSomething().then(doSomethingElse);
若是你知道正常答案,那麼我要恭喜你,你是一位 promises 大拿,你徹底能夠再也不繼續閱讀這篇文件。數組
另外 99.99% 的小夥伴們,大家纔是正義。沒有一我的在 twitter 上給出正確答案,甚至我本身也被 #3 的答案驚到了。恩,即便這道題是我本身出的。promise
正確答案在這篇文章的結尾,在此以前,我但願首先探究一下爲什麼爲什麼 promises 如此複雜,而且爲什麼有這麼多人,不管是新手仍是專家,都被它坑到了。同時我還會給出一個我自認爲很是獨特的視角,可讓 promises 變的更加容易理解。同時,我很是確信在瞭解這些以後,promises 並不會再難以理解。瀏覽器
不過在開始這些以前,讓咱們先了解一些 promises 的基礎知識。緩存
若是你閱讀了 promises 的一些相關文獻,你會發現有一個詞 金字塔問題 常常出現。它描述的是大量的回調函數慢慢向右側屏幕延伸的一種狀態。異步
Promises 的確解決了這個問題,而且不只僅是縮進問題。就像在 Callback Hell的救贖 中描述的同樣,回調函數真正的問題在於他剝奪了咱們使用 return 和 throw 這些關鍵字的能力。相反,咱們的整個代碼流程都是基於反作用的: 一個函數會附帶調用其餘函數。
原文關於反作用的描述並不能很直觀的進行理解,建議參考 WIKI 。簡單來講就是一個函數除了會返回一個值以外,還會修改函數之外的狀態如全局變量等等。實際上全部異步調用均可以視爲帶有反作用的行爲。譯者注。
而且實際上,回調更加惱人的是,他會將咱們一般在大部分編程語言中能得到的 堆棧 破壞。編寫代碼時若是沒法獲取堆棧就像開車時沒有剎車同樣: 不到用的時候,你不會知道它有多重要。
Promises 給予咱們的就是在咱們使用異步時丟失的最重要的語言基石: return, throw 以及堆棧。可是想要 promises 可以提供這些便利給你的前提是你知道如何正確的使用它們。
一些同窗試圖經過用 卡通 來描述 promises,或者試圖用語言去描述它: 「哦,你能夠把它做爲一個異步的值進行傳遞。」
我認爲這些解釋並不會有很大的幫助。對我來講,promises 徹底是一種代碼結構和流程。所以我認爲直接展現一些常見的錯誤而且演示如何修復它們更可以說明問題。我說這些問題是 「新手問題」 ,這意味着 「雖然你如今是一個新手,孩子,可是立刻你會變成一位專家」。
小插曲: 「promises」 對於不一樣的人有不一樣的理解和觀點,可是在這篇文章中我特指 正式標準 ,在現代瀏覽器中暴露爲 window.Promise。雖然並不是全部瀏覽器都有 windows.Promise,可是能夠尋找一些 pollyfill ,好比 Lie 是目前體積最小的兼容標準的庫。
新手錯誤 #1: promise版的金字塔問題
觀察你們如何使用 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.status == 409) { localdb.get(element.doc._id).then(function (resp) { localdb.remove(resp._id, resp._rev).then(function (resp) { // et cetera...
是的,實際上你能夠像使用回調同樣使用 promises,恩,就像用打磨機去削腳趾甲同樣,你確實能夠這麼作。
而且若是你覺得這樣的錯誤只限於初學者,那麼你會驚訝於我其實是在黑莓官方開發者博客上看到上面的代碼。老的回調風格的習慣難以消滅。(至開發者: 抱歉選了你的例子,可是你的例子將會有積極的教育意義)
正確的風格應該是這樣:
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 的強大能力之一。每個函數只會在前一個 promise 被調用而且完成回調後調用,而且這個函數會被前一個 promise 的輸出調用,稍後咱們在這塊作更多的討論。
新手錯誤 #2: WTF, 用了 promises 後怎麼用 forEach?
這裏是大多數人對於 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,這意味着第二個方法不會等待全部 documents 都執行 db.remove()。實際上他不會等待任何事情,而且可能會在任意數量的文檔被刪除後執行!
這是一個很是隱蔽的 bug,由於若是 PouchDB 刪除這些文檔足夠快,你的 UI 界面上顯示的會完成正常,你可能會徹底注意不到有什麼東西有錯誤。這個 bug 可能會在一些古怪的競態問題或一些特定的瀏覽器中暴露出來,而且到時可能幾乎沒有可能去定位問題。
簡而言之,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()會以一個 promises 數組爲輸入,而且返回一個新的 promise。這個新的 promise 會在數組中全部的 promises 都成功返回後才返回。他是異步版的 for 循環。
而且 Promise.all() 會將執行結果組成的數組返回到下一個函數,好比當你但願從 PouchDB 中獲取多個對象時,會很是有用。此外一個更加有用的特效是,一旦數組中的 promise 任意一個返回錯誤,Promise.all() 也會返回錯誤。
新手錯誤 #3: 忘記使用 .catch()
這是另外一個常見的錯誤。單純的堅信本身的 promises 會永遠不出現異常,不少開發者會忘記在他們的代碼中添加一個 .catch()。然而不幸的是這也意味着,任何被拋出的異常都會被吃掉,而且你沒法在 console 中觀察到他們。這類問題 debug 起來會很是痛苦。
相似 Bluebird 之類的 Promise 庫會在這種場景拋出 UnhandledRejectionError 警示有未處理的異常,這類狀況一旦發現,就會形成腳本異常,在 Node 中更會形成進程 Crash 的問題,所以正確的添加 .catch() 很是重要。 譯者注
爲了不這類討厭的場景,我習慣於像下面的代碼同樣使用 promise:
somePromise().then(function () { return anotherPromise(); }).then(function () { return yetAnotherPromise(); }).catch(console.log.bind(console)); // <-- this is badass
即便你堅信不會出現異常,添加一個 catch() 總歸是更加謹慎的。若是你的假設最終被發現是錯誤的,它會讓你的生活更加美好。
新手錯誤 #4:使用 「deferred」
這是一個我常常能夠看到的錯誤,以致於我甚至不肯意在這裏重複它,就像害怕 Beetlejuice 同樣,僅僅是提到它的名字,就會召喚出來更多。
簡單的說,promises 擁有一個漫長而且戲劇化的歷史,Javascript 社區花費了大量的時間讓其走上正軌。在早期,deferred 在 Q,When,RSVP,Bluebird,Lie等等的 「優秀」 類庫中被引入, jQuery 與 Angular 在使用 ES6 Promise 規範以前,都是使用這種模式編寫代碼。
所以若是你在你的代碼中使用了這個詞 (我不會把這個詞重複第三遍!),你就作錯了。下面是說明一下如何避免它。
首先,大部分 promises 類庫都會提供一個方式去包裝一個第三方的 promises 對象。舉例來講,Angular的 $q 模塊容許你使用 $q.when 包裹非 $q 的 promises。所以 Angular 用戶能夠這樣使用 PouchDB promises.
$q.when(db.put(doc)).then(/* ... */); // <-- this is all the code you need
另外一種策略是使用構造函數聲明模式,它在用來包裹非 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(/* ... */)
完工!咱們戰勝了可怕的 def….啊哈,抓到本身了。:)
關於爲什麼這是一種反模式更多的內容,請查看 Bluebird 的 promise anti-patterns wiki 頁
新手錯誤 #5:使用反作用調用而非返回
下面的代碼有什麼問題?
somePromise().then(function () { someOtherPromise(); }).then(function () { // Gee, I hope someOtherPromise() has resolved! // Spoiler alert: it hasn't. });
好了,如今是時候討論一下關於 promises 你所須要知道的一切。
認真的說,這是一個一旦你理解了它,就會避免全部我說起的錯誤的古怪的技巧。你準備好了麼?
就如我前面所說,promises 的奇妙在於給予咱們之前的 return 與 throw。可是在實踐中這究竟是怎麼一回事呢?
每個 promise 都會提供給你一個 then() 函數 (或是 catch(),實際上只是 then(null, ...) 的語法糖)。當咱們在 then() 函數內部時:
somePromise().then(function () { // I'm inside a then() function! });
咱們能夠作什麼呢?有三種事情:
就是這樣。一旦你理解了這個技巧,你就理解了 promises。所以讓咱們逐個瞭解下。
返回另外一個 promise
這是一個在 promise 文檔中常見的使用模式,也就是咱們在上文中提到的 「composing promises」:
getUserByName('nolan').then(function (user) { return getUserAccountById(user.id); }).then(function (userAccount) { // I got a user account! });
注意到我是 return
第二個 promise,這個 return
很是重要。若是我沒有寫 return
,getUserAccountById()
就會成爲一個反作用,而且下一個函數將會接收到 undefined
而非 userAccount
。
返回一個同步值 (或者 undefined)
返回 undefined 一般是錯誤的,可是返回一個同步值其實是將同步代碼包裹爲 promise 風格代碼的一種很是讚的手段。舉例來講,咱們對 users 信息有一個內存緩存。咱們能夠這樣作:
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! });
是否是很贊?第二個函數不須要關心 userAccount 是從同步方法仍是異步方法中獲取的,而且第一個函數能夠很是自由的返回一個同步或者異步值。
不幸的是,有一個不便的現實是在 JavaScript 中無返回值函數在技術上是返回 undefined,這就意味着當你本意是返回某些值時,你很容易會不經意間引入反作用。
出於這個緣由,我我的養成了在 then() 函數內部 永遠返回或拋出 的習慣。我建議你也這樣作。
拋出同步異常
談到 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() 會接收到一個同步異常,而且若是 後續的 promise 中出現異步異常,他也會接收到。再強調一次,這個函數並不須要關心這個異常是同步仍是異步返回的。
這種特性很是有用,所以它可以在開發過程當中幫助定位代碼問題。舉例來講,若是在 then() 函數內部中的任何地方,咱們執行 JSON.parse(),若是 JSON 格式是錯誤的,那麼它就會拋出一個異常。若是是使用回調風格,這個錯誤極可能就會被吃掉,可是使用 promises,咱們能夠輕易的在 catch() 函數中處理它了。
好了,如今你已經瞭解了讓 promises 變的超級簡單的技巧,如今讓咱們聊一聊一些特殊場景。
這些錯誤之因此被我歸類爲 「進階」 ,是由於我只見過這些錯誤發生在對 promises 已經有至關深刻了解的開發者身上。可是爲了解決文章最開始的謎題,咱們必須討論一下這些錯誤。
進階錯誤 #1:不知道 Promise.resolve()
如我上面所列舉的,promises 在封裝同步與異步代碼時很是的有用。然而,若是你發現你常常寫出下面的代碼:
new Promise(function (resolve, reject) { resolve(someSynchronousValue); }).then(/* ... */);
你會發現使用 Promise.resolve 會更加簡潔:
Promise.resolve(someSynchronousValue).then(/* ... */);
它在用來捕獲同步異常時也極其的好用。因爲它實在是好用,所以我已經養成了在我全部 promise 形式的 API 接口中這樣使用它:
function somePromiseAPI() { return Promise.resolve().then(function () { doSomethingThatMayThrow(); return 'foo'; }).then(/* ... */); }
切記:任何有可能 throw 同步異常的代碼都是一個後續會致使幾乎沒法調試異常的潛在因素。可是若是你將全部代碼都使用 Promise.resolve() 封裝,那麼你老是能夠在以後使用 catch() 來捕獲它。
相似的,還有 Promise.reject() 你能夠用來返回一個馬上返回失敗的 promise。
Promise.reject(new Error('some awful error'));
進階錯誤 #2:catch() 與 then(null, ...) 並不是徹底等價
以前我說過 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) 這種形式時,rejectHandler 並不會捕獲由 resolveHandler 引起的異常。
鑑於此,我我的的習慣是不適用 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 接口時,是一對很是好的組合。 pouchdb-plugin-seed 項目中有一些 示例 能夠幫助你入門。
進階錯誤 #3:promises vs promises factories
當咱們但願執行一個個的執行一個 promises 序列,即相似 Promise.all() 可是並不是並行的執行全部 promises。
你可能天真的寫下這樣的代碼:
function executeSequentially(promises) { var result = Promise.resolve(); promises.forEach(function (promise) { result = result.then(promise); }); return result; }
不幸的是,這份代碼不會按照你的指望去執行,你傳入 executeSequentially() 的 promises 依然會並行執行。
其根源在於你所但願的,實際上根本不是去執行一個 promises 序列。依照 promises 規範,一旦一個 promise 被建立,它就被執行了。所以你實際上須要的是一個 promise factories 數組。
function executeSequentially(promiseFactories) { var result = Promise.resolve(); promiseFactories.forEach(function (promiseFactory) { result = result.then(promiseFactory); }); return result; }
我知道你在想什麼:「這是哪一個見鬼的 Java 程序猿,他爲啥在說 factories?」 。實際上,一個 promises factory 是十分簡單的,它僅僅是一個能夠返回 promise 的函數:
function myPromiseFactory() { return somethingThatCreatesAPromise(); }
爲什麼這樣就能夠了?這是由於一個 promise factory 在被執行以前並不會建立 promise。它就像一個 then 函數同樣,而實際上,它們就是徹底同樣的東西。
若是你查看上面的 executeSequentially() 函數,而後想象 myPromiseFactory 被包裹在 result.then(...) 之中,也許你腦中的小燈泡就會亮起。在此時此刻,對於 promise 你就算是悟道了。
進階錯誤 #4:好了,若是我但願得到兩個 promises 的結果怎麼辦
有時候,一個 promise 會依賴於另外一個,可是若是咱們但願同時得到這兩個 promises 的輸出。舉例來講:
getUserByName('nolan').then(function (user) { return getUserAccountById(user.id); }).then(function (userAccount) { // dangit, I need the "user" object too! });
爲了成爲一個優秀的 Javascript 開發者,而且避免金字塔問題,咱們可能會將 user 對象存在一個更高的做用域中的變量裏:
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" });
這樣是沒問題的,可是我我的認爲這樣作有些雜牌。我推薦的策略是拋棄成見,擁抱金字塔:
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 的重點。
進階錯誤 #5:promises 穿透
最後,這個錯誤就是我開頭說的 promises 謎題所影射的錯誤。這是一個很是稀有的用例,而且可能徹底不會出如今你的代碼中,可是的的確確震驚了我。
你認爲下面的代碼會打印出什麼?
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) { console.log(result); });
若是你認爲它會打印出 bar,那麼你就錯了。它實際上打印出來的是 foo!
發生這個的緣由是若是你像 then() 傳遞的並不是是一個函數(好比 promise),它實際上會將其解釋爲 then(null),這就會致使前一個 promise 的結果會穿透下面。你能夠本身測試一下:
Promise.resolve('foo').then(null).then(function (result) { console.log(result); });
添加任意數量的 then(null),它依然會打印 foo。
這實際上又回到了我以前說的 promises vs promise factories。簡單的說,你能夠直接傳遞一個 promise 到 then() 函數中,可是它並不會按照你指望的去執行。then() 是指望獲取一個函數,所以你但願作的最多是:
Promise.resolve('foo').then(function () { return Promise.resolve('bar'); }).then(function (result) { console.log(result); });
這樣他就會如咱們所想的打印出 bar。
所以記住:永遠都是往 then() 中傳遞函數!
如今咱們瞭解了關於 promsies 全部的知識(或者接近!),咱們應該能夠解決文章最開始我提出的謎題了。
這裏是謎題的全部答案,我以圖形的格式展現出來方便你查看:
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() 函數而且在瀏覽器中本身試試看。
聲明:在這些例子中,我假定 doSomething() 和 doSomethingElse() 均返回 promises,而且這些 promises 表明某些在 JavaScript event loop (如 IndexedDB, network, setTimeout) 以外的某些工做結束,這也是爲什麼它們在某些時候表現起來像是並行執行的意義。這裏是一個模擬用的 JSBin。
關於更多 promises 的進階用法,能夠參考個人 promise protips cheat sheet
Promises 是很是讚的。若是你還在使用回調模式,我強烈建議你切換到 promises。你的代碼會變的更少,更優雅,而且更加容易理解。
若是你不相信我,這裏是證實:a refactor of PouchDB’s map/reduce module,使用 promises 替換回調。結果是:新增 290 行,刪除 555 行。
順帶一提,寫出那使人討厭的回調代碼的人。。是我!所以這是我第一次領會到 promises 的力量,同時我感謝其餘 PouchDB 的貢獻者們教導我這樣作。
固然了,promises 並不是完美。雖然它的確比回調模式要好,可是這樣說就比如說給你肚子來一拳會比在你牙齒上踹一腳好。的確,它是會略有優點,可是若是你有選擇,你會二者都盡力避免。
做爲回調模式的升級版,promises 依然難以理解而且容易誤用,證實之一就是我不得不寫下這篇博文。初學者與專家都很容易常常將它用錯,而且真要說的話,並不是是他們的問題。問題在於 promises 的使用模式與咱們寫同步代碼很是相似,可是又不盡然。
我也認爲 promises 的確難以理解而且容易誤用,證實之一就是我不得不翻譯這篇博文。 譯者注
老實說,你不該該須要去學一堆晦澀難懂的規則和新的 API 去作在同步代碼中咱們已經熟稔的 return,catch,throw 和 for 循環。在你的腦中不該該老是要提醒本身要區分有兩套並行的系統。
期待 async/await
這是我在 「Taming the asynchronous beast with ES7」 中提到的重點,在這篇文章中我探究了 ES7 的 async/await 關鍵字,以及它們是如何將 promises 更深度的結合入語言。再也不會要求咱們去編寫僞同步的代碼(以及一個假的 catch() 函數,雖然像,可是並不是是 catch),ES7 將會容許咱們使用真正的 try/catch/return 關鍵字,就像咱們在 CS 101 上學的同樣。
這對於 Javascript 語言來講是一個大福音。由於即便到最後,只要咱們的工具不告訴咱們作錯了,這些 promise 反模式依然會一直出現。
從 JavaScript 的歷史來看,我認爲公正的評價來講 JSLint 與 JSHint 對社區的貢獻是高於 JavaScript: The Good Parts 的,雖然他們包含的信息其實是相同的。可是它們的區別在於 被告知你在你代碼中犯的錯誤 與你去閱讀一本書籍,去理解其餘人犯的錯誤。
ES7 的 async/await 的美妙在於,你的錯誤會被做爲語法或者編譯器錯誤提示出來,而不是運行時的 bug。不過就目前而言,瞭解 promise 能夠作什麼以及如何在 ES5 與 ES6 中正確的使用它們依然是有必要的。
所以當我意識到,就像 JavaScript: The Good Parts 同樣,這篇博文可能只會有很是有限的影響的時候,我但願當你發現其餘人在犯一樣的錯誤的時候,你能夠將這篇博文提供給他們。由於如今依然有不少同窗須要認可: 「I have a problem with promises!」
更新:有人告知我 Bluebird 3.0 將會 打印警告 來避免我文中所列舉的這些錯誤。所以當咱們還在等待 ES7 時,使用 Bluebird 會是另外一個極好的方案。