[譯文]如何避開 async/await 地獄

原文地址 How to escape async/await hell數組

async/await把咱們從回調地獄中解放了出來,可是,人們也對其很有微詞.由於隨之而來致使了async/await地獄的誕生.promise

在這篇文章,我會試圖解釋什麼是async/await地獄,另外我也會分享一些避開它們的方法.併發

什麼是 async/await 地獄?

當咱們在編寫JavaScript異步代碼的時候,人們常常在一個接着一個的函數調用前面添加await關鍵字.這會致使性能問題,由於在一般狀況下,一個語句的執行並不依賴前一個語句的執行,可是由於添加了await關鍵字,你仍舊須要等待前一個語句執行完才能執行一個語句.異步

一個 async/await 地獄的例子.

假設你寫一段代碼用來購買披薩和飲料,這段代碼以下所示.async

(async () => {
  const pizzaData = await getPizzaData()    // async call
  const drinkData = await getDrinkData()    // async call
  const chosenPizza = choosePizza()    // sync call
  const chosenDrink = chooseDrink()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
  await addDrinkToCart(chosenDrink)    // async call
  orderItems()    // async call
})()
複製代碼

從表面上看,這段代碼語法是正確的,而且可以運行.可是,這並非一個好的實現,由於它剔除了併發執行.接下來讓咱們瞭解一下這段代碼是作什麼的,這樣咱們更加明確其中的問題所在.函數

解釋

咱們把這段代碼包裹在了一個異步的當即執行函數裏面.下面的事情會按次序發生:oop

  1. 得到披薩的列表.
  2. 得到飲料的列表.
  3. 從披薩列表中選擇披薩.
  4. 從飲料列表中選擇飲料.
  5. 把選擇的披薩加入購物車
  6. 把選擇的飲料加入購物車.
  7. 確認訂單

錯誤在哪裏?

就像我在前面提到的那樣,全部的語句都是一行接着一行執行的,這裏不存在併發執行的狀況.讓咱們仔細想一想,爲何咱們在獲取飲料列表以前須要等待披薩列表的返回?咱們應該嘗試同時獲取飲料和披薩的列表.然而,當咱們須要選擇披薩的時候,咱們須要先獲取披薩的列表.飲料也是如此.性能

所以咱們肯定,披薩相關的工做和飲料相關的工做可以同時執行,可是披薩相關的每一步工做須要按次序執行.(順序執行)ui

另一個壞例子

這段JavaScript代碼會得到購物車裏面的物品,而後發送確認訂單的請求.spa

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

在這種狀況下,for循環在執行下一輪循環以前須要等待當前的sendRequest()執行完成.然而,實際上咱們不須要等待.咱們但願儘量快的發送全部請求而後等待他們都執行完成.

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

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

若是你忘記在異步函數調用的前面添加await關鍵字,這時函數開始執行了,這意味着await並非函數執行的必要條件.這個異步函數會返回一個promise,這個promise咱們能夠在以後使用.

(async () => {
  const value = doSomeAsyncTask()
  console.log(value) // an unresolved promise
})()

複製代碼

結果就是,編譯器不知道你須要等待這個函數執行完成,所以編譯器會在這個異步任務尚未完成的時候退出這個程序.所以咱們須要await關鍵字.

promise有一個有趣的性質是你能夠在前面的代碼獲得這個promise, 而後在後面的代碼中等待這個promise完成.這是從async/await地獄中解脫的關鍵.

(async () => {
  const promise = doSomeAsyncTask()
  const value = await promise
  console.log(value) // the actual value
})()
複製代碼

正如你所見到的那樣,doSomeAsyncTask()返回了一個promise.這個時候,doSomeAsyncTask()已經開始執行了.爲了獲得這個promise的結果值,咱們能夠在這個promise前面添加await,JavaScript將會馬上停在這裏再也不執行下一行代碼,直到得到了這個promise的返回值,再執行下一行代碼.

如何逃離 async/await 地獄?

你應該跟隨如下步驟來逃離async/await地獄.

找出全部以來其餘語句執行的語句

在咱們第一個例子裏面,咱們在選擇披薩和飲料.於是咱們得出結論,在選擇披薩以前,咱們須要得到披薩的列表.同時,在把披薩加入到購物車以前,咱們須要選擇披薩.能夠認爲這三個步驟是互相依賴的,咱們不能在前一個步驟完成以前執行下一個任務. 可是,若是咱們拓寬一下眼界,就會發現選擇披薩並不會依賴於選擇飲料,咱們能夠同時選擇他們.這就是機器能作的比咱們更好的地方. 至此,咱們已經發現了一些語句依賴於其餘的語句執行,可是另一些語句不依賴.

把相互依賴執行的語句整合在異步函數裏面.

正如咱們所看到的,選擇披薩須要幾個互相依賴的語句,如得到披薩列表,選擇其中一個披薩而後添加到購物車中.咱們應該把這些語句整合在一個異步函數裏面.這樣咱們將會獲得兩個異步函數,selectPizza()selectDrink()

併發的執行這些異步函數.

咱們將利用event loop的優點來併發執行這些非阻塞異步函數.爲了達成這個目標,咱們經常使用的方法是先返回promise而後使用Promise.all方法.

讓咱們改正這個例子

根據前面提到的三個步驟,咱們把他們運用的咱們的例子中.

async function selectPizza() {
  const pizzaData = await getPizzaData()    // async call
  const chosenPizza = choosePizza()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // async call
  const chosenDrink = chooseDrink()    // sync call
  await addDrinkToCart(chosenDrink)    // async call
}

(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // async call
})()

// 我更喜歡下面這種實現.

(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // async call
})()

複製代碼

如今咱們已經把這些語句整合到兩個函數中,在每個函數裏面,每個語句的執行依賴於前一個函數的執行.而後咱們併發的執行selectPizza()selectDrink().

在第二個例子裏面,咱們須要解決未知數量的promise.解決這種狀況很是簡單:咱們只須要建立一個數組而後把promise存入其中.而後使用Promise.all()方法,就可以併發的等待全部的promise返回結果.

async function orderItems() {
  const items = await getCartItems()    // async call
  const noOfItems = items.length
  const promises = []
  for(var i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i])    // async call
    promises.push(orderPromise)    // sync call
  }
  await Promise.all(promises)    // async call
}

// 我更喜歡下面這種實現 

async function orderItems() {
  const items = await getCartItems()    // async call
  const promises = items.map((item) => sendRequest(item))
  await Promise.all(promises)    // async call
}
複製代碼

我喜歡這篇文章可以幫助你脫離async/await基礎使用者的行列,同時可以幫助你提升你的程序性能. 若是你喜歡這篇文章,但願可以點贊並收藏.

相關文章
相關標籤/搜索