[譯] 如何逃離 async/await 地獄

async/await 將咱們從回調地獄中解脫,但人們的濫用,致使了 async/await 地獄的誕生。javascript

本文將闡述什麼是 async/await 地獄,以及逃離 async/await 地獄的幾個方法。前端

什麼是 async/await 地獄

進行 JavaScript 異步編程時,你們常常須要逐一編寫多個複雜語句的代碼,並都在調用語句前標註了 await。因爲大多數狀況下,一個語句並不依賴於前一個語句,可是你仍不得不等前一個語句完成,這會致使性能問題。java

一個 async/await 地獄示例

思考一下,若是你須要寫一段腳本預訂一個披薩和一杯飲料。腳本可能會是這樣的:android

(async () => {
  const pizzaData = await getPizzaData()    // 異步調用
  const drinkData = await getDrinkData()    // 異步調用
  const chosenPizza = choosePizza()    // 同步調用
  const chosenDrink = chooseDrink()    // 同步調用
  await addPizzaToCart(chosenPizza)    // 異步調用
  await addDrinkToCart(chosenDrink)    // 異步調用
  orderItems()    // 異步調用
})()
複製代碼

表面上看起來沒什麼問題,這段代碼也能夠執行。可是它並非一個好的實現,由於它沒有考慮併發性。讓咱們瞭解一下這段代碼是怎麼運行的,這樣才能夠肯定問題所在。ios

解釋

咱們將這段代碼包裹在一個異步的 IIFE 當即執行函數 中。準確的執行順序以下:git

  1. 獲取披薩列表。
  2. 獲取飲料列表。
  3. 從列表中選擇一份披薩。
  4. 從列表中選擇一杯飲料。
  5. 將選中披薩加入購物車。
  6. 將選中飲料加入購物車。
  7. 將購物車內物品下單。

哪裏出問題了?

如我以前所強調過的,全部語句都會逐一執行。此處並沒有併發操做。仔細想一下:爲何咱們要在獲取披薩列表完成後纔去獲取飲料列表呢?兩個列表應該一塊兒獲取。可是咱們在選擇披薩時,確實須要在這以前已獲取飲料列表。飲料同理。github

所以,咱們能夠總結出,披薩相關的事務與飲料相關事務能夠併發發生,可是披薩相關事務內部的獨立步驟須要繼發進行(逐一進行)。編程

另外一個糟糕實現的例子

這段代碼將獲取購物車內的東西,併發起一個請求下單。後端

async function orderItems() {
  const items = await getCartItems()    // 異步調用
  const noOfItems = items.length
  for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i])    // 異步調用
  }
}
複製代碼

在這種狀況下,for 循環須要等待 sendRequest() 函數完成後才能進行下一個迭代。事實上,咱們不須要等待。咱們想要儘快發送全部請求,而後等待全部請求執行完畢。數組

但願如今你能夠更理解 async/await 地獄是什麼,以及它對你的程序性能影響有多麼嚴重。如今,我想問你一個問題。

若是咱們忘了 await 關鍵字會怎樣?

若是你在調用一個異步函數時忘了使用 await 關鍵字,該函數就會當即開始執行。這意味着 await 對於函數的執行來講不是必需的。異步函數會返回一個 promise 對象,你能夠稍後使用這個 promise。

(async () => {
  const value = doSomeAsyncTask()
  console.log(value) // 一個未完成的 promise
})()
複製代碼

不使用 await 調用異步函數的另外一個後果是,編譯器不知道你想等待這個函數執行完成。所以編譯器將在異步任務完成以前就退出程序。所以咱們確實須要 await 關鍵字。

promise 有一個好玩的特性,你能夠在一行代碼中獲得一個 promise 對象,在另外一行代碼中獲得這個 promise 的執行結果。這是逃離 async/await 地獄的關鍵。

(async () => {
  const promise = doSomeAsyncTask()
  const value = await promise
  console.log(value) // 實際的返回值
})()
複製代碼

如你所見,doSomeAsyncTask() 返回了一個 promise 對象。此時 doSomeAsyncTask() 開始執行。咱們使用 await 關鍵字來獲取 promise 對象的執行結果,並告訴 JavaScript 不要當即執行下一行代碼,而是等待 promise 執行完成再執行下一行代碼。

如何逃離 async/await 地獄?

你須要遵循如下步驟:

找到依賴其它語句執行結果的語句

在第一個示例中,咱們選擇了一份披薩和一杯飲料。能夠推斷出在選擇一份披薩前,咱們須要先得到全部披薩的列表。在將選擇的披薩加入購物車以前,咱們須要先選擇一份披薩。所以咱們能夠說這三個步驟是互相依賴的。咱們不能在前一件事完成以前作下一件事。

可是若是把問題看得更普遍一些,咱們能夠發現選披薩並不依賴選飲料,所以咱們能夠並行選擇。這方面,機器能夠比咱們作的更好。

所以咱們已經發現有一些語句依賴於其它語句的執行,有些則不依賴。

將互相依賴的語句包裹在 async 函數中

如咱們所見,選擇披薩包括瞭如獲取披薩列表,選擇披薩,將所選披薩加入購物車等依賴語句。咱們應該將這些語句包裹在一個 async 函數中。這樣咱們獲得了兩個 async 函數,selectPizza()selectDrink()

併發執行 async 函數

而後咱們能夠利用事件循環併發執行這些非阻塞 async 函數。有兩種經常使用模式,分別是優先返回 promises 和使用Promise.all 方法

讓咱們來修改一下示例

遵循如下三個步驟,將它們應用到咱們的示例中。

async function selectPizza() {
  const pizzaData = await getPizzaData()    // 異步調用
  const chosenPizza = choosePizza()    // 同步調用
  await addPizzaToCart(chosenPizza)    // 異步調用
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // 異步調用
  const chosenDrink = chooseDrink()    // 同步調用
  await addDrinkToCart(chosenDrink)    // 異步調用
}

(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // 異步調用
})()

// 我更喜歡這種方法

(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // 異步調用
})()
複製代碼

如今咱們將語句分組到兩個函數中。在函數內部,每一個語句依賴於前一個語句的執行。而後咱們併發執行兩個函數 selectPizza()selectDrink()

在第二個例子中,咱們須要處理未知數量的 promise。解決這種狀況很容易:建立一個數組,將 promise push 進去。而後使用 Promise.all() 咱們就能夠並行等待全部的 promise 處理完畢。

async function orderItems() {
  const items = await getCartItems()    // 異步調用
  const noOfItems = items.length
  const promises = []
  for(var i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i])    // 異步調用
    promises.push(orderPromise)    // 同步調用
  }
  await Promise.all(promises)    // 異步調用
}
複製代碼

但願本文能夠幫你提升 async/await 的基礎水平並提高應用的性能。

若是喜歡本文,請點個喜歡。

也請分享到 Fb 和 Twitter。若是想獲取文章更新,能夠在 TwitterMedium 上關注我。有任何問題能夠在評論中指出。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索