1. 程式人生 > >【轉】We have a problem with promises

【轉】We have a problem with promises

mem eas 也會 同步方法 side pos lld node-js should

這是我看的自認為最好的一篇講解如何使用Promise的文章,原文地址:http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/

用Javascript的小夥伴們,是時候承認了,關於 promises 我們一直存在著問題。並非說 promises 本身有問題,Promises/A+ 是極好的。

就我過去數年觀察大量 PouchDB API 以及其他 promise-heavy API 的使用者們與這些 API 的搏鬥中我發現,最大的問題是:

大部分使用 promises 的小夥伴們並沒有真正的理解它

如果你不認同這個觀點,可以看看我最近在 twitter 上出的這道題:

Q: 下面的四種 promises 的區別是什麽

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

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

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);

如果你知道正常答案,那麽我要恭喜你,你是一位 promises 大拿,你完全可以不再繼續閱讀這篇文件。

另外 99.99% 的小夥伴們,你們才是正義。沒有一個人在 twitter 上給出正確答案,甚至我自己也被 #3 的答案驚到了。恩,即使這道題是我自己出的。

正確答案在這篇文章的結尾,在此之前,我希望首先探究一下為何為何 promises 如此復雜,並且為何有這麽多人,無論是新手還是專家,都被它坑到了。同時我還會給出一個我自認為非常獨特的視角,可以讓 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 的奇妙在於給予我們以前的 returnthrow。但是在實踐中這到底是怎麽一回事呢?

每一個 promise 都會提供給你一個 then() 函數 (或是 catch(),實際上只是 then(null, ...) 的語法糖)。當我們在 then() 函數內部時:

somePromise().then(function () {
  // I‘m inside a then() function!
});

我們可以做什麽呢?有三種事情:

  1. return 另一個 promise
  2. return 一個同步的值 (或者 undefined)
  3. throw 一個同步異常

就是這樣。一旦你理解了這個技巧,你就理解了 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 是非常贊的。如果你還在使用回調模式,我強烈建議你切換到 promises。你的代碼會變的更少,更優雅,並且更加容易理解。

如果你不相信我,這裏是證明:a refactor of PouchDB’s map/reduce module,使用 promises 替換回調。結果是:新增 290 行,刪除 555 行。

順帶一提,寫出那令人討厭的回調代碼的人。。是我!因此這是我第一次領會到 promises 的力量,同時我感謝其他 PouchDB 的貢獻者們教導我這樣做。

當然了,promises 並非完美。雖然它的確比回調模式要好,但是這樣說就好比說給你肚子來一拳會比在你牙齒上踹一腳好。的確,它是會略有優勢,但是如果你有選擇,你會兩者都盡力避免。

作為回調模式的升級版,promises 依然難以理解並且容易誤用,證明之一就是我不得不寫下這篇博文。初學者與專家都很容易經常將它用錯,並且真要說的話,並非是他們的問題。問題在於 promises 的使用模式與我們寫同步代碼非常類似,但是又不盡然。

我也認為 promises 的確難以理解並且容易誤用,證明之一就是我不得不翻譯這篇博文。 譯者註

老實說,你不應該需要去學一堆晦澀難懂的規則和新的 API 去做在同步代碼中我們已經熟稔的 returncatchthrow 和 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 會是另一個極好的方案。

【轉】We have a problem with promises