Hossam Mourad

When is the "for...of" statement useful in JavaScript

August 06, 2021


Let’s start with an example. You have a list of city names and you want to iterate over that list to hit an API with each city name to get more details about that given city:

const cityNames = ["alexandria", "ottawa", "berlin"]

What comes to mind first is to use forEach:

cityNames.forEach(async (city) => {
  const { data } = await fetch(
    `http://API/cities?name=${city}`
  );
  ...
});

Here is the problem, forEach will not wait for the fetch promise to be resolved. It will continue executing the code. Let’s place a couple of console logs and see what we get:

cityNames.forEach(async city => {
  const { data } = await fetch(`http://API/cities?name=${city}`)
  console.log(`${city} data`, data)
})
console.log("after forEach")

Run this and you will get this in the logs:

after forEach
alexandria data, ...
ottawa data, ...
berlin data, ...

Sometimes this behavior is fine but what if you actually want to wait until your loop is done resolving all promises in each iteration? This is when you should use for...of in that case.

The same above example but now using for...of:

for (const city of cityNames) {
  const { data } = await fetch(`http://API/cities?name=${city}`)
  console.log(`${city} data`, data)
}
console.log("after for...of")

Run this and you will get:

alexandria data, ...
ottawa data, ...
berlin data, ...
after for...of

A couple of notes to be aware of though:

  • There is another statement called for...in. Here is the difference: for...of gives you back the item in each iteration. for...in gives you back the index
  • If an error happened in the for...of loop. The execution of the rest of the code in the block will stop. So don’t forget to wrap your for...of loop inside a try...catch block
  • There is a more mainstream solution (the one you will see a lot on Stack Overflow) to this by keep using the forEach but wrapping it in a promise and resolve the promise manually at the end of the loop. I’m not a big fan of that other solution. I think it’s more code and complexity that can be avoided by just using another built-in JavaScript statement