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

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. Sign up below to get all my new posts on these topics:

Subscribe for more Node and JavaScript posts!

No spam ever. Unsubscribe any time.