• How to escape async/await hell
  • Aditya Agarwal
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Colafornia
  • Proofreader: Starriers Whuzxq

Async /await frees us from callback hell, but people abuse it to create async/await hell.

This article will explain what async/await hell is and a few ways to escape async/await hell.

What is async/await hell

When you do asynchronous JavaScript programming, you often need to code multiple complex statements one by one and label “await” them before calling them. Because most of the time, a statement doesn’t depend on a previous statement, but you still have to wait for the previous statement to complete, this can cause performance problems.

An example of async/await hell

Imagine if you needed to write a script to order a pizza and a drink. The script might look something like this:

(async() = > {const pizzaData = await getPizzaData()    // Asynchronous invocation
  const drinkData = await getDrinkData()    // Asynchronous invocation
  const chosenPizza = choosePizza()    // Synchronous call
  const chosenDrink = chooseDrink()    // Synchronous call
  await addPizzaToCart(chosenPizza)    // Asynchronous invocation
  await addDrinkToCart(chosenDrink)    // Asynchronous invocation
  orderItems()    // Asynchronous invocation}) ()Copy the code

On the surface, it looks fine, and the code will execute. But it’s not a good implementation because it doesn’t take concurrency into account. Let’s take a look at how this code works so we can identify the problem.

explain

We wrapped this code in an asynchronous IIFE immediate execution function. The exact order of execution is as follows:

  1. Get the 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 the selected beverage to your cart.
  7. Place orders for items in the shopping cart.

What went wrong?

As I emphasized earlier, all statements are executed one by one. There are no concurrent operations. Think about it: Why would we want to get the drink list after getting the pizza list is complete? Both lists should be fetched together. But when we choose a pizza, we do need to get the drink list in advance. Same with drinks.

Therefore, we can conclude that pizza-related transactions and beverage related transactions can occur concurrently, but separate steps within the pizza-related transactions need to be followed (one by one).

Another example of a bad implementation

This code will retrieve the contents of the shopping cart and initiate a request to place an order.

async function orderItems() {
  const items = await getCartItems()    // Asynchronous invocation
  const noOfItems = items.length
  for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i])    // Asynchronous invocation}}Copy the code

In this case, the for loop waits for the sendRequest() function to complete before proceeding to the next iteration. In fact, we don’t have to wait. We want to send all requests as quickly as possible and then wait for all requests to complete.

Hopefully now you have a better understanding of what async/await hell is and how badly it affects your application’s performance. Now, I want to ask you a question.

What happens if we forget the await keyword?

If you forget to use the await keyword when calling an asynchronous function, the function will start executing immediately. This means that await is not required for the execution of the function. The asynchronous function returns a Promise object that you can use later.

(async() = > {const value = doSomeAsyncTask()
  console.log(value) // An unfinished promise}) ()Copy the code

Another consequence of calling an asynchronous function without await is that the compiler does not know that you want to wait for the function to complete. So the compiler will exit the program before the asynchronous task completes. So we really need the await keyword.

Promises have a fun feature where you can get a promise object in one line of code and the result of that promise in another line of code. This is the key to escaping async/await hell.

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

As you can see, doSomeAsyncTask() returns a Promise object. At this point doSomeAsyncTask() starts executing. We use the await keyword to get the execution result of the Promise object and tell JavaScript not to execute the next line of code immediately, but to wait for the promise execution to complete.

How to escape async/await hell?

You need to follow these steps:

Find statements that depend on the execution results of other statements

In the first example, we chose a pizza and a drink. It follows that before choosing a pizza, we need to get a list of all the pizzas. We need to select a pizza before adding the selected pizza to our shopping cart. So we can say that these three steps are interdependent. We can’t do the next thing before the first thing is done.

But if we look at the problem more broadly, we can see that choosing pizza doesn’t depend on choosing a drink, so we can choose in parallel. Machines can do this better than we can.

So we’ve seen that some statements depend on the execution of other statements, and some do not.

Wrap interdependent statements in async functions

As we have seen, select pizza includes dependency statements such as get pizza list, select pizza, add selected pizza to cart, etc. We should wrap these statements in an async function. This gives us two async functions, selectPizza() and selectDrink().

Execute async functions concurrently

We can then execute these non-blocking async functions concurrently using an event loop. There are two common patterns: Return promises first and use the promise.all method.

Let’s modify the example

Follow these three steps to apply them to our example.

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

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

(async() = > {const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // Asynchronous invocation}) ()// I prefer this method

(async() = > {Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // Asynchronous invocation}) ()Copy the code

Now we group the statements into two functions. Inside a function, each statement depends on the execution of the previous statement. We then execute two functions selectPizza() and selectDrink() concurrently.

In the second example, we need to deal with an unknown number of promises. The solution to this situation is easy: Create an array and push the promise into it. We then use promise.all () to wait for all promises to complete in parallel.

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

Hopefully this article will help you improve the basic level of async/await and improve the performance of your application.

If you like this article, please like it.

Please share them on Fb and Twitter. For updates, follow me on Twitter and Medium. Any questions can be pointed out in the comments.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.