The introduction of the async
and await
keywords made it easier to handle asynchronous tasks in JavaScript. However, developers should be aware of some problematic aspects when it comes to using them inside loops.
What’s the problem? Basically, async
and await
work differently depending on which type of loop they are in.
Inside the native loops (i.e. for
, for...of
, for...in
, while
, do...while
), the async
keyword blocks the loop until the asynchronous task is over. For example:
async function printPokemonNames() {
const pokemonIDs = [25, 120, 237];
for (const pokemonID of pokemonIDs) {
const pokemon = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemonID}`
).then(response => response.json());
console.log(pokemon.name);
}
}
printPokemonNames();
This code makes a query to the PokéAPI to print the name of three Pokémon. Each time it calls the asynchronous fetch
method, the entire code pauses until the request is completed.
It works like this:
- Get the info for the first Pokémon using the API request
- Wait until the request is complete
- Print the name of the first Pokémon
- Get the info for the second Pokémon using the API request
- Wait until the request is complete
- Print the name of the second Pokémon
- Get the info for the third Pokémon using the API request
- Wait until the request is complete
- Print the name of the third Pokémon
On the other hand, when you loop over an array (or another iterable) using a method like forEach
, map
, reduce
, etc. that requires a callback function. In those cases, the await
keyword pauses the callback for that iteration but doesn’t stop the entire loop.
Let’s rewrite the previous example using forEach
:
const pokemonIDs = [25, 120, 237];
pokemonIDs.forEach(async function(pokemonID) {
const pokemon = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemonID}`
).then(response => response.json());
console.log(pokemon.name);
});
Here, the call to fetch
blocks the rest of the callback function (i.e. the call to console.log
in this example), but it doesn’t prevent forEach
from processing the second Pokémon even before completing the previous one. It works like this:
- Get the info for the first Pokémon using the API request
- Get the info for the second Pokémon using the API request
- Get the info for the third Pokémon using the API request
- Wait until the first request is complete, then print the name of the first Pokémon
- Wait until the second request is complete, then print the name of the second Pokémon
- Wait until the third request is complete, then print the name of the third Pokémon
An important aspect of this approach is that the three calls to fetch
now happen practically at the same time. When you use a native loop, calls to fetch
never happen simultaneously because the entire loop is blocked.
Which one is the best option? It depends! If you need to optimize speed, it might be a good idea to use a method.
However, if you have an array with hundreds or thousands of elements, and the asynchronous function is fetch
, it means that you’ll make hundreds or thousands of HTTP requests at the same time! There’s no one-size-fits-all solution.