How to await in a loop in JavaScript

By

Learn how to use await inside a loop in JavaScript with for...of and for...in, why you must be in an async function, and why forEach and map will not work.

~~~

To await inside a loop in JavaScript, use a for...of loop inside an async function. The loop pauses at every iteration until the promise you’re awaiting resolves.

Here is how to use the for...of loop to iterate an array and await inside the loop:

const fun = (prop) => {
  return new Promise(resolve => {
    setTimeout(() => resolve(`done ${prop}`), 1000)
  })
}

const go = async () => {
  const list = [1, 2, 3]

  for (const prop of list) {
    console.log(prop)
    console.log(await fun(prop))
  }

  console.log('done all')
}

go()

You need to place the loop in an async function, then you can use await, and the loop stops the iteration until the promise we’re awaiting resolves.

The iterations run one after the other. Each fun() call takes 1 second, so this whole loop takes 3 seconds, and done all prints last. That’s exactly what you want when each step depends on the previous one, like writing rows to a database in order.

You can do the same with a for...in loop to iterate on the properties of an object:

const go = async () => {
  const obj = { a: 1, b: 2, c: 3 }

  for (const prop in obj) {
    console.log(prop)
    console.log(await fun(prop))
  }

  console.log('done all')
}

go()

You could also use while or do..while or regular for loops with this same structure.

Why doesn’t forEach work?

You can’t await with Array.forEach() or Array.map(). This looks like it should work, but it doesn’t:

const list = [1, 2, 3]

list.forEach(async (item) => {
  console.log(await fun(item))
})

console.log('done all')

The output is:

done all
done 1
done 2
done 3

done all prints first. The await here only pauses the callback function, not the loop. forEach fires all three callbacks immediately and moves on, without waiting for any of them.

This is a common source of bugs: the code after the loop runs before the work inside the loop has finished.

What if you want the calls to run in parallel?

Sometimes waiting for each item in sequence is wasted time, because the calls don’t depend on each other. In that case, start them all at once and await the whole batch with Promise.all():

const go = async () => {
  const list = [1, 2, 3]
  const results = await Promise.all(list.map(fun))

  console.log(results) // [ 'done 1', 'done 2', 'done 3' ]
  console.log('done all')
}

go()

This takes 1 second total instead of 3, because the three promises run at the same time. Use for...of when order matters, Promise.all() when it doesn’t.

~~~

Related posts about js: