Bruno Buccolo

How to use async/await with forEach

JavaScript today is way more readable and pleasant to write now that we have async/await.

However, I still get caught up with it from time to time.

Below is an example of a handler implementation on a serverless project. The code needs to process all the records that are received.

export const handler = async event => {
  event.Records.forEach(record => {        <--- error
    const message = JSON.parse(record)

    await process(message)
  })
}

You can only use await on the body of an async function. We can’t await if the function that is being passed to forEach is not async.

That makes sense, but I was not sure to make it work.

So I read a post that reimplemented forEach, which looked super weird. Then I found a nice StackOverflow answer that was promising (pun), but my head was still confused so I couldn’t parse the relevant bits.

To make progress, I added the missing async:

export const handler = async event => {
  event.Records.forEach(async record => {
    const message = JSON.parse(record)

    await process(message)
  })
}

The code compiles, but nothing is processed becaused I still need to await those functions somehow.

Continuing the thought process, if I wanted to do something with them, I had to use map. Finally, I remembered that Promises.all can handle exactly that.

export const handler = async event => {
  const promises = event.Records.map(async record => {
    const message = JSON.parse(record)

    await process(message)
  })

  await Promise.all(promises)
}

That’s is it! You can’t use forEach with async/await.

You can either collect all the async functions to resolve with Promises.all in parallel or you can do it sequentially using for...of:

export const handler = async event => {
  for (let record of event.Records) {
    const message = JSON.parse(record)

    await process(message)
  }
}

Cheers!

© 2011– Bruno Buccolo (@buccolo) | Made in São Paulo ☂