如何避免 async/await 地獄

簡評:async/await 寫着很爽,不過要注意這些問題。

async/await 讓咱們擺脫了回調地獄,可是這又引入了 async/await 地獄的問題。編程

圖片描述

什麼是 async/await 地獄數組

在 Javascript 中進行異步編程的時候,人們老是使用不少 await 語句,不少時候咱們的語句並不須要依賴於以前的語句,這樣就會致使性能問題。promise

async/await 地獄的例子併發

咱們試着寫一個購買披薩和飲料的程序:異步

(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
})()

這段代碼運行沒有問題。可是不是一個好的實現,由於這增長了沒必要要的等待。async

說明異步編程

咱們已經將咱們的代碼封裝在異步 IIFE 中,按照下面的順序執行:函數

獲得披薩名單
獲取飲料列表
從列表中選擇一個披薩
從列表中選擇一種飲料
將選中的披薩加入購物車
將選擇的飲品加入購物車
訂購購物車中的物品性能

問題this

這裏有個問題爲何從列表中選擇披薩這個動做要等待獲取飲料列表?這兩個是沒什麼關聯的操做。其中的關聯操做有兩組:

獲取披薩列表 -》 選擇披薩 -》 選擇披薩加入購物車

獲取飲料列表 -》 選擇飲料 -》 選擇飲料加入購物車

這兩組操做應該是併發執行的。

再來看一個更差的例子

這個 Javascript 代碼片斷將購物車中的商品併發出訂購請求。

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,這意味着執行該功能不須要等待。異步函數將直接返回一個 promise,你能夠稍後使用。

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

或者是程序不清楚你想要等待函數執行完,直接退出不會完成這個異步任務。因此咱們須要使用 await 這個關鍵字。

promise 有一個有趣的屬性,你能夠在某行代碼中獲取 promise,而後在其餘地方中等待它 resolve,這是解決 async/await 地獄的關鍵。

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

如你所見 doSomeAsyncTask 直接返回一個 Promise 同時這個異步函數 doSomeAsyncTask 已經開始執行,爲了獲得 doSomeAsyncTask 的返回值,咱們須要 await 來告訴

應該如何避免 async/await 地獄

首先咱們須要知道哪些命名是有先後依賴關係的。
而後將有依賴關係的系列操做進行分組合併成一個異步操做。
同時執行這些異步函數。
咱們來重寫這寫例子:

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
})()

// Although I prefer it this way 

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

咱們將語句分紅兩個函數。在函數內部,每一個語句都依賴於前一個語句的執行。而後咱們同時執行這兩個函數 selectPizza()和selectDrink() 。

在第二個例子中咱們須要處理未知數量的 Promise。處理這個問題很是簡單,咱們只須要建立一個數組將全部 Promise 存入其中,使用 Promise.all() 方法並行執行:

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
}
相關文章
相關標籤/搜索