DEV Community

Better ways to use async functions in Javascript

Aditya Bhattad on July 21, 2024

If you are into web development, there is a 100% chance you have used at least some async functions. There are different ways to use async function...
Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️
// Start both fetches concurrently
  const fastDataPromise =  fetchFastData();
  const slowDataPromise =  fetchSlowData();

  // Wait for both promises to resolve
  const [fastData, slowData] = await Promise.all([fastDataPromise, slowDataPromise]);
Enter fullscreen mode Exit fullscreen mode

With the existence of Promise.all, this is definitely the "right" way of doing this, but it isn't technically the only one:

   const fastDataPromise =  fetchFastData()
   const slowDataPromise =  fetchSlowData()

   const fastData = await fastDataPromise
   const slowData = await slowDataPromise
Enter fullscreen mode Exit fullscreen mode

As you can probably tell, this approach can lead to much less readable code, which is why a solution like Promise.all makes sense.

But we can also implement our own Promise.all this way:

const simple_promise_all = async (promises) => {
   for (i in promises) {
      promises[i] = await promises[i]
   }
}
Enter fullscreen mode Exit fullscreen mode

To make the code shorter, this version just writes the results back into the same array and ignores rejected promises, but it shows how there isn't actually any magic behind Promise.all.

Collapse
 
vicariousv profile image
AndrewBarrell1

Your simple_promise_all isn't equal to Promise.all though, no?

Your's doesn't reject if any reject, and it waits for each resolve/reject sequentially, where as Promise.all tries to resolve all of them at the same time.

In your example, with 10 requests that each take approx. 2 seconds to resolve, you would need to wait 20 seconds. With Promise.all it would be 2 seconds.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Your's doesn't reject if any reject

Yes, as I pointed out, I ignored that feature to keep my example short.

and it waits for each resolve/reject sequentially, where as Promise.all tries to resolve all of them at the same time.

Both approaches resolve all of the promises at the same time, one of them just hides the implementation so you don't really think about how it's done.

In your example, with 10 requests that each take approx. 2 seconds to resolve, you would need to wait 20 seconds. With Promise.all it would be 2 seconds.

It would be 2 seconds (give or take) in both cases. awaiting a promise doesn't pause the others, so while the loop is waiting for the first one, the other promises are all still running. Once the first one finishes, the other promises will all be finished too or just about to, so awaiting them is basically just collecting their results.

Thread Thread
 
vicariousv profile image
AndrewBarrell1

You are right, I brain farted 😂

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

Just for fun, here's a more complete implementation:

const promise_all = (promises) => new Promise(async (resolve, reject) => {
   result = []

   promises
      .filter(value => value instanceof Promise)
      .each(promise => promise.catch(reject))

   for (i in promises) {
      const p = promises[i]
      if (p instanceof Promise)
         result[i] = await p
      else
         result[i] = p
   }]
   resolve(result)
})
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
richard_remer_b146e9be08d profile image
Richard Remer

If you remove the "p instanceof Promise" check it still works fine, but it also handles the case where "p" is a then-able; that is a promise that doesn't extend Promise. Await can be used on synchronous values without trouble, so no need to branch.

Collapse
 
adityabhattad profile image
Aditya Bhattad

Good point!

Collapse
 
vicariousv profile image
AndrewBarrell1 • Edited

A very important note for using timeouts with Promise.race()

When you have a timeout, while the promise may resolve or reject, the timeout will keep running, keeping the execution alive.

So if the success case passes after 1 second and the program's logic ends, the timeout set for 5 seconds will keep going for another 4 seconds and only then will the process' execution end.

This can be really important in serverless Lambdas where you pay for run time.

const timeoutRace = async (promise, ms) => {
  let timer = null

  const timeoutFn = new Promise((resolve, reject) => {
    timer = setTimeout(reject, ms)
  })

  try{
    const myRace = await Promise.race([promise, timeoutFn])

    clearTimeout(timer)

    return myRace
  } catch (error) {
    console.error("race error")
  }
}

timeoutRace(myFetchAPICall, 10000)
Enter fullscreen mode Exit fullscreen mode

In the above example, if the fetch API call succeeds after 500ms, the process ends.

Without clearing the timer as in your example, the process would continue for another 9.5 seconds before ending.

(On mobile, forgive any typos/autocorrects)

Collapse
 
adityabhattad profile image
Aditya Bhattad

Definitely something to keep it mind!

Collapse
 
davidnussio profile image
David Nussio

A better way could be using effect.website/

Collapse
 
adityabhattad profile image
Aditya Bhattad

Will definitely check it out, Thanks!

Collapse
 
yourakshaw profile image
Ayush Kumar Shaw

I definitely learned something new, Aditya! Thanks for this amazing blog post! Had to bookmark this one! Thanks again! 💯

Collapse
 
adityabhattad profile image
Aditya Bhattad

I'm really glad, you got to learn!

Collapse
 
milan_lietava_c6d0525f5e6 profile image
Milan Lietava

Thanks for sharing Aditya. Helped me a lot. Nice brief examples as well.

Collapse
 
andrew-saeed profile image
Andrew Saeed

I have learned a lot! Thanks, Aditya!

Collapse
 
marmeden profile image
eneasmarin

Very interesting Aditya!

Collapse
 
sufyan_syed profile image
Sufyan Syed

Recently learnt promises and was looking for practical use cases of these methods. This is the best explanation I found. Thanks a lot!!! 💛🖤

Collapse
 
tonny0831 profile image
tonny_x_bit

In my opinion async func is grammar in ES-6. So I think it's better method in javascript.