DEV Community

Corey Cleary
Corey Cleary

Posted on • Originally published at coreycleary.me

Executing arrays of async/await JavaScript functions in series vs. concurrently

Originally published at coreycleary.me. This is a cross-post from my content blog. I publish new content every week or two, and you can sign up to my newsletter if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets and other freebies.

When dealing with an array of async/await functions (which return Promises), it can be tricky to figure out how to execute them all in series (one-at-a-time) and how to call them in concurrently (not one-at-a-time, executed during overlapping time periods).

Maybe you've been trying to execute them in series, but they end up executing out of order. Or maybe you've been trying to execute them concurrently, but they end up executing one-at-a-time, in series.

In this post we'll explain both methods.

That way, if you have a requirement where you need to ensure each function resolves in order, you will be able to and won't be scratching your head trying to figure out why the functions are resolving out of order...

And if you need them to be concurrent, you'll be able to do so and get them to resolve faster than if you were doing it in series.

Series

The easiest way to run an array of async/await functions in series is to use for...of. This will execute them in order, one at a time, and will wait for each to resolve.

const asyncA = async () => {
  return 'a'
}

const asyncB = async () => {
  return 'b'
}

const asyncC = async () => {
  return 'C'
}

const list = [asyncA, asyncB, asyncC]

for (const fn of list) {
  await fn() // call function to get returned Promise
}

Concurrently

For executing concurrently, I recommend using Promise.all(). Remember, async/await functions are syntactic sugar around Promises, so you can use Promise.all() on them.

const asyncA = async () => {
  return 'a'
}

const asyncB = async () => {
  return 'b'
}

const asyncC = async () => {
  return 'C'
}

const list = [asyncA, asyncB, asyncC]

await Promise.all(list.map(fn => fn()))  // call each function to get returned Promise

Of course, if you want the return values of these async functions, you could do:

const responses = await Promise.all(list.map(fn => fn()))

// destructured example
const [a, b, c] = await Promise.all(list.map(fn => fn()))

Quick note: this post is just covering the "happy path" for now (i.e. - no errors/Promise rejections) for both in-series and concurrent. I've got another post planned for soon that will deal with more robust error handling. Just be aware for now that with Promise.all(), it will reject with the first promise that rejects.

Execution

An in case you were confused about the definitions of the two, execution-wise, this is what series vs concurrent looks like:

Technically, the concurrently executed functions will not all kickoff at the exact same time, but for all intents and purposes, this is what it looks like.

And if you want to see this in code:

const wait = time => {
  return new Promise(resolve => setTimeout(resolve, time))
}

const someFn = async (item) => {
  await wait(2000)
  console.log(item)
}

// in series
for (const item of ['first', 'second', 'third']) {
  await someFn(item)
}
// 0s
// 2s - 'first'
// 4s - 'second'
// 6s - 'third'


// concurrently
await Promise.all(['first', 'second', 'third'].map(itm => someFn(itm)))
// 0s
// 2s (roughly) - 'first', 'second', 'third'

Wrapping up

Next time you need to remember how to execute the two types, reference this post. And if you haven't tried using Promise.all() before, try it out next time you have a scenario where you don't need all your async functions to execute in order. It's a nice speed boost, which depending on the scenario, could be a nice boost for your end-user.

Feel like you haven't quite totally grasped async/await and Promises? I publish new posts every week or two about JavaScript and Node.js, including dealing with asynchronous scenarios. Here's that link again to subscribe to my newsletter!

Latest comments (4)

Collapse
 
matsaleh profile image
Matthew Walker

Nice article, thanks!

I'd also highly recommend using the caolan/async package. It implements asynchronous functions such as each (iteration), whilst and until (conditional), and waterfall (chaining), among many others. It also has utilities for composing those functions, and many of them have series and parallel variants where appropriate.

It supports async/await and promises, as well as callbacks (when you must). I think it's a great additional tool for async programming with JS, especially with Node.js.

Hope this helps, and cheers!

Collapse
 
dance2die profile image
Sung M. Kim

Thank you Corey for the post~ πŸ™‚

I can think of using the principles in this article and apply on a web page.

Say, if one needs to display a part of webpage as quickly as possible, then we can use the sequential route.

If all contents need to be displayed at once, then concurrently.

Would the application work by chance?

Collapse
 
ccleary00 profile image
Corey Cleary

Hey Sung, yeah I think thats accurate. For fetching things needed for certain parts of the webpage, doing it in series/sequentially will work as long as you load them in the right order (i.e. - the component that needs its data fetched first has its function run before the others)

Collapse
 
dance2die profile image
Sung M. Kim

Thank you for the clarification, Corey~ 🀜