DEV Community

Cover image for Avoid async/await hell
Judicaël Andriamahandry
Judicaël Andriamahandry

Posted on

Avoid async/await hell

Anders Drange Image

Escape from the async/await hell or avoid the chained async tasks

First of all let's talk about async/await hell , and then we'll see how to avoid it

TL;DR

This is the async/await hell we're talking about, it's just as an example, there's more.

const user = await getUser(id);
const items = await getItems();
return {
  user,
  items,
};
Enter fullscreen mode Exit fullscreen mode

Here we're fetching the specific user details and then fetching a list of items after. You may say, what's wrong with that?, the thing is that we're waiting to get the user details before getting the list of items, althought fetching the list of items doesn't depends on the user list. So why bother waiting if we could run them in parallel?. In addition that reduce the perfomance.

Now, consider this example:

  (async () => {
    const user = await getUser(id);
    const items = await getItems();
    return {
      user
       items
    }
  })()
Enter fullscreen mode Exit fullscreen mode

We wrapped it using IIFE, but still this code is executed one by one

So what can we do to avoid that.

We have solutions:

we could fix it using something like

In this case, the user and items returns a promise so could await for the promise to fulfil when returning the value

const user = userPromise(id);
const items = itemsPromise();
return {
  user: await whenUser,
  items: await whenItems,
};
Enter fullscreen mode Exit fullscreen mode

But I prefer using Promise.all it's a lot cleaner

const [user, items] = await Promise.all([getUser(id), getItems()]);
return { user, items };
Enter fullscreen mode Exit fullscreen mode

simple, elegant 😄 and up to twice as fast because the Promise.all execute all of them concurrently.

You can learn more about Promise.all on MDN

Note: Worth noting that Promise.all will return either when all promises succeed or the first one rejects, whereas Promise.allSettled will wait until every promise has either resolved or rejected

Top comments (26)

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Nicely put. One note:

await Promise.all() will throw an exception if either promise rejects rather than returning.

Collapse
 
kaleman15 profile image
Kevin Alemán

If you care about the actual resolve/rejection, take a look at Promise.allSettled, which doesn't throw on rejects

Collapse
 
judionit profile image
Judicaël Andriamahandry

yes yes, it was on the note too

Promise.allSettled will wait until every promise has either resolved or rejected

Collapse
 
igormelo profile image
Igor Melo

Usually you will want the user to try again if some of then failed

Collapse
 
thumbone profile image
Bernd Wechner • Edited

I have to admit I am left wondering who would write code like this to begin with:

const user = await getUser(id);
const items = await getItems();
return { user,  items, };
Enter fullscreen mode Exit fullscreen mode

it defeats the whole point of async!

And of course anyone who's using async functions must perforce have read some intro on Promises (and I admit I had to read many about 20 times before I think I finally got it) will knowabout Promise.all() and Promise.allSettled() as they indeed the standard means of waiting on several promises at once.

I'd be cautious with this:

and up to twice as fast because we're running all the promises in parallel

It is mostly true. But it does hide some asusmptions too. It's important to understand that an async function consumes time in two ways:

  1. Executing Javascript code
  2. Waiting on Browser promises (generally HTTP requests, maybe user input, maybe a websocket response whatever, but the browser is off doing something).

The difference is crucial because 1. remains blocking regardless of its async status (the JavaScript engine not supporting true parallelism or multthreading at all), while category 2. work isn't and actually forms the wellspring of true Promises (from the Browser to the JavScript engine).

And that is relevant to that comment, because it can happen that two async functions poorly written, when run in parallel yield little benefit at all. That would be exceptional of course and falls within the purvey of the claim ( "up to twice" and I said that mostly true, in fact I'll go one better, it's quite insightful, because in the best case the two functions have identical cost in time and then you'd halve the net wait, as long as their time cost is in category 2.)

The reason for that distinction is very relevant when a function is expensive because of JavaScript processing time. In that instance however, the setTimeout function becomes very important as a means of relaxing your expensive code's strangle-hold on the JavaScript engine.

I discuss some of that here:

dev.to/thumbone/deferring-to-the-u...

Collapse
 
judionit profile image
Judicaël Andriamahandry

yes yes definitely true, learned some on your comments, thanks

Collapse
 
gixxerblade profile image
Steve Clark 🤷‍♀️

...up to twice as fast because we're running all the promises in parallel

Are you sure it's running in parallel?

Collapse
 
gixxerblade profile image
Steve Clark 🤷‍♀️

At a high level, Node.js falls into the category of concurrent computation. This is a direct result of the single-threaded event loop being the backbone of a Node.js application. The event-loop repeatedly takes an event and then sequentially executes all listeners interested in that event. The event loop never runs two pieces of JavaScript in parallel.
bytearcher.com/articles/parallel-v...

Collapse
 
judionit profile image
Judicaël Andriamahandry

concurrently to be exact

Collapse
 
tomdoestech profile image
Tom Nagle

It doesn't run in parallel, that would require multiple threads. Promise.all executes promises concurrently.

Collapse
 
judionit profile image
Judicaël Andriamahandry

yes, thanks, fixed it

Collapse
 
brucewan profile image
xiaotian

good job

Collapse
 
elijahtrillionz profile image
Elijah Trillionz

Nicely said.
Thanks 😊

Collapse
 
henimbola profile image
WhisPro

Nicely explained.

Collapse
 
piso02 profile image
Rojo Rasoamahefa

Thank you Judicaël, interesting :)

Collapse
 
judionit profile image
Judicaël Andriamahandry

thank you thank you

Collapse
 
uzair004 profile image
Muhammad Uzair

What about error handling in this case, while using multiple awaits we could have error handling on response of each result , what would happen if promise got rejected, response is not same as what we expect ?

Collapse
 
judionit profile image
Judicaël Andriamahandry

so as mentioned Promise.all will asynchronously rejects with the value of the promise that rejected, whether or not the other promises have resolved

Collapse
 
gladyshenley profile image
GladysHenley

It’s actually not that hard to implement a promisify function of our own, how it works. dua to get married soon to someone you love

Collapse
 
harshjoeyit profile image
Harshit Gangwar • Edited

Yep! That's the rule of thumb if async requests are intependent then there is no harm in calling them in parallel.

Collapse
 
judionit profile image
Judicaël Andriamahandry

they're not executed in parallel but concurrently

Collapse
 
harshjoeyit profile image
Harshit Gangwar

Thanks for correcting me! I googled and found the difference.
Cheers!