How to avoid async/await hell

Short comment: async/await is cool to write, but pay attention to these issues.

async/await gets us out of callback hell, but it introduces the problem of async/await hell.

write picture description here

What is async/await hell

When doing asynchronous programming in Javascript, people always use a lot of await statements, and many times our statements do not need to depend on previous statements, which will cause performance problems.

Example of async/await hell

Let's try to write a program that buys pizza and drinks:

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

This code runs without problems. But not a good implementation as this adds unnecessary wait.

illustrate

We've wrapped our code in an asynchronous IIFE, which executes in the following order:

  1. get pizza list
  2. Get a list of drinks
  3. Choose a pizza from the list
  4. Choose a drink from the list
  5. Add selected pizza to cart
  6. Add selected drink to cart
  7. order items in cart

question

Here's a question. Why does the action to select pizza from the list wait for the drink list to be fetched? These two operations are unrelated. There are two groups of associated operations:

Get pizza list -> select pizza -> select pizza and add to cart

Get Beverage List -> Select Beverage -> Select Beverage Add to Cart

These two sets of operations should be executed concurrently.

Let's look at an even worse example.
This Javascript snippet takes the items in the shopping cart and makes an order request.

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

In this case the for loop must wait for the sendRequest() function to complete before proceeding to the next iteration. However, we don't need to wait. We hope to send all requests as soon as possible. Then we can wait for all requests to complete.

Now that you know more about async/await hell, let's consider another question

What if we forget the await keyword?

If you forget to use await when calling an async function, it means that no await is required to execute the function. Async functions will directly return a promise that you can use later.

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

Or the program is not clear that you want to wait for the function to finish executing, and exiting directly will not complete the asynchronous task. So we need to use the await keyword.

Promises have an interesting property, you can get a promise in one line of code and wait for it to resolve elsewhere, which is the key to solving async/await hell.

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

As you can see doSomeAsyncTask returns a Promise directly and the asynchronous function doSomeAsyncTask has already started to execute. In order to get the return value of doSomeAsyncTask, we need await to tell

How to avoid async/await hell

First we need to know which names are dependent on each other.
Then group the dependent series of operations into one asynchronous operation.
Execute these asynchronous functions concurrently.
Let's rewrite this write example:

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

We split the statement into two functions. Inside a function, each statement depends on the execution of the previous statement. Then we execute both functions selectPizza() and selectDrink() at the same time.

In the second example we need to handle an unknown number of Promises. Dealing with this problem is very simple, we just need to create an array to store all the promises in it, and use the Promise.all() method to execute them in parallel:

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
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325529182&siteId=291194637