await,你可以丟掉promise鏈了

有了async/await,你可以丟掉promise鏈了

異步函數可能會一直存在,但有些人認為 async/await 可能會被拋棄。

為什麼?

一個常見的誤解是 async/await 和 promise 是完全不同的東西。

但其實 async/await 是基於 promise 的。

不要因為你使用了 promise 就被 promise 鏈給野蠻綁架了。

在本文中,我們將瞭解 async/await 如何讓開發人員的生活變得更輕鬆,以及為什麼要停止使用 promise 鏈。

讓我們來看看一個 promise 鏈的例子:

getIssue()
.then(issue => getOwner(issue.ownerId))
.then(owner => sendEmail(owner.email, 'Some text'))

現在讓我們看看 async/await 的等效代碼:

const issue = await getIssue()
const owner = await getOwner(issue.ownerId)
await sendEmail(owner.email, 'Some text')

它看起來就像簡單的語法糖,對嗎?

與大多數人一樣,我發現自己的代碼看起來很簡單、乾淨、易於閱讀。但是,在修改代碼時,似乎比預期的困難一些。

但這一點也不奇怪,這正是 promise 鏈的問題所在。下面讓我們看看這是為什麼。

易於閱讀,易於維護

假設我們需要對之前的代碼做出一個很小的修改(例如,我們需要在電子郵件內容中提及問題編號,比如“Some text #issue-number”)。

我們該怎麼做?對於 async/await 版本,改起來很簡單:

const issue = await getIssue()
const owner = await getOwner(issue.ownerId)
await sendEmail(owner.email, `Some text #${issue.number}`) // tiny change here

前兩行不受影響,第三行只需要稍微改動一點點。

那麼 promise 鏈版本呢?

在.then() 中,我們可以訪問 owner,但不能訪問 issue。看看,promise 鏈從這裡開始就變得有點混亂了。我們可以試著這樣修改:

getIssue()
.then(issue => {
return getOwner(issue.ownerId)
.then(owner => sendEmail(owner.email, `Some text #${issue.number}`))
})

正如你所看到的,一個小的調整就需要修改好幾行代碼(如 getOwner(issue.ownerId))。

代碼在不斷髮生變化

在開發新功能時尤其如此。例如,如果我們需要將異步調用 getSettings() 返回的結果包含在電子郵件內容中,該怎麼辦?

它可能看起來像這樣:

const settings = await getSettings() // we added this
const issue = await getIssue()
const owner = await getOwner(issue.ownerId)
await sendEmail(owner.email,
`Some text #${issue.number}. ${settings.emailFooter}`) // minor change here

如果使用 promise 鏈該怎樣實現?可能是這樣:

Promise.all([getIssue(), getSettings()])
.then(([issue, settings]) => {
return getOwner(issue.ownerId)
.then(owner => sendEmail(owner.email,
`Some text #${issue.number}. ${settings.emailFooter}`))
})

但是,對我來說,這些代碼顯得有點亂。每當我們需要做出修改時,都需要修改很多代碼,這實在太噁心了!

因為我不想再嵌套 then() 調用,我可以並行地調用 getIssue() 和 getSettings(),所以我使用了 Promise.all(),然後進行一些解構。確實,這個版本與 await 版本相比更好,因為它可以並行運行 ,但它仍然難以閱讀。

我們是否可以優化 await 版本,讓它可以並行運行而不需要犧牲代碼的可讀性?讓我們來看看:

const settings = getSettings() // we don't await here
const issue = await getIssue()
const owner = await getOwner(issue.ownerId)
await sendEmail(owner.email,
`Some text #${issue.number}. ${(await settings).emailFooter}`) // we do it here

我刪除了 settings 右側的 await,並在 sendEmail() 前面加上了 await。我創建了一個 promise,但在需要用到這個值之前不需要等待。與此同時,其他代碼可以並行運行。就這麼簡單!

你不需要 Promise.all() 了

我已經演示瞭如何在不使用 Promise.all() 的情況下輕鬆有效地並行運行 promise。這意味著你不再需要 Promise.all() 了,對吧。

有些人可能會爭辯說,還有一個情況,也就是當你有一個值數組時,你需要將它映射到一個 promise 數組。例如,你有一個要讀取的文件名的數組,或者你需要下載的 URL 的數組,等等。

我認為他們錯了。我的建議是使用外部庫來處理併發。例如,我會使用 bluebird 中的 Promise.map(),因為它支持設置併發限制。如果我要下載 N 個文件,可以指定同時下載的文件個數不超過 M 個。

你可以在任何地方使用 await

async/await 可以幫你簡化你要做的事情。想象一下,如果使用 promise 鏈,下面這些表達式有多複雜。但是如果使用 async/await,它們就會簡單得多。

const value = await foo() || await bar()
const value = calculateSomething(await foo(), await bar())

還說服不了你?

假設你對代碼可閱讀性和易維護性不感興趣,相反,你更喜歡複雜性,那麼好吧。

在代碼中使用 promise 鏈時,開發者每次在調用 then() 時都會創建新函數。這會佔用更多內存,而且這些函數總是處在另一個上下文中。因此,這些函數變成了閉包,這使垃圾回收變得更加困難。此外,這些匿名函數通常會汙染堆棧跟蹤。

現在,我們討論的是堆棧跟蹤:現在有一個提議(https://docs.google.com/document/d/13Sy_kBIJGP0XT34V1CV3nkWya4TwYx9L3Yv45LdGB6Q/edit)用於為異步函數實現更好的堆棧跟蹤。

只要開發人員堅持只使用異步函數和異步生成器,並且不會手動編寫 promise 代碼,因為如果使用了 promise 鏈,就無法實現更好的堆棧跟蹤。

這也是總是使用 async/await 的另一個原因!

如何遷移

首先:開始使用異步函數並停止使用 promise 鏈。

其次,你可能已經發現 Visual Studio Code 可以非常方便地幫你實現遷移(視頻地址:https://twitter.com/umaar/status/1045655069478334464)。

結論

  • async/await 已得到廣泛支持,除非你需要支持 IE。
  • async/await 代碼具有更好的可讀性和可維護性。
  • 出於一些技術原因,最好是隻使用 async/await。
  • 藉助 Visual Studio Code 或其他 IDE,你可以輕鬆地遷移現有的 promise 鏈代碼!


分享到:


相關文章: