Using await in loop cause performance issues
Tags: javascript, async, performance, promises
Published: 2021-05-18 18:14:36


Recently, I learned the hard way that **await** is not the solution to every promises.

At work, I had to write a piece of code that was looping for a **lots** of elements.

Bascially, it was looping over hundreds of elements, and was doing an HTTP request for each in order to retrieve some vitals informations.

It was something like that : 

```js
  //...
 const datas = [];

  for (const element of elements) {
    const result = await axios.get('https://pokeapi.co/api/v2/pokemon/ditto');
    datas.push(result);
  }
  // Use datas ...
```

This is a really simplified exemple using a free Pokemon API (We all have our favorite API's 🙈). 
I didn't notice myself that it was causing a performance issue, it first came as an Eslint error : 

```code
    Unexpected `await` inside a loop.
```
🤔🤔🤔

It was time to dig and to follow the [documentation link](https://eslint.org/docs/rules/no-await-in-loop).

And just to make sure Eslint wasn't lying to me (You should trust him 100%), I did some testings ...

## The test

Going back to our previous exemple, but with some **console.time** to evaluate the actual time it take for our loop.

```js
const axios = require('axios');
const elements = new Array(45);

async function fetchPokemons() {
  const datas = [];

  console.time('Wrong way');
  for (const element of elements) {
    const result = await axios.get('https://pokeapi.co/api/v2/pokemon/ditto');
    datas.push(result);
  }

  console.timeEnd('Wrong way');

}

fetchPokemons();
```

Here is the Node code sample I used, feel free to try it out yourself.

It would be painful to make you guess how much time it took for our loop to finish, so here you go :

**Between 12 and 13 seconds.**

```code
    Wrong way: 13.191s
```

Doesn't sound *that* bad for 45 HTTP calls, but let's see how it goes if we refactor as Eslint told us.


## The refactor

```js
async function fetchPokemons() {
  const promises = [];

  console.time('Nice way');
  
  for (const element of elements) {
    const result = axios.get('https://pokeapi.co/api/v2/pokemon/ditto');
    promises.push(result);
  }
  
  const results = await Promise.all(promises);
  const actualDatas = results.map((result) => result.data); // We need an extra loop to extract the data, and not having the requests stuff
  
  console.timeEnd('Nice way');
}

fetchPokemons();
```

So ... What happened ?

Well, we basically removed the *await*, and pushed all our *unresolved* promises into an array. Then we simply await for all of them to resolved, and we extract the datas.

*Isn't it the same thing ?*

Well .. Not really. Before we dive into the explanation, could you take a quick guess on how much time it take for us to gather all the datas ?

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

**Between 0.8 and 1.5 seconds.**

```code
    Nice way: 1.448s
```

🤯

Did we just cut down the time by **10** ? Yes.

## Explanation

It's pretty simple, previously we were waiting for each requests to resolve before launching the next one : 

- Launch first
- Wait N seconds until it resolved
- Launch seconds
- Wait N seconds until it resolved
- ...

Time add up **a lot** as we saw.

Now, it look like this : 

- Launch first
- Launch second
- ...
- Wait for what's left to resolve

You got it ? By the time we were looping and launching everything, some - if not most, promises have already resolved !

## Conclusion

Now you'll think twice before awaiting in a loop.

If you are a bit lost and don't really get what was happening here, I wrote an [article](https://dev.to/spartakyste/the-promises-guide-i-would-have-loved-as-a-junior-developper-3621) that cover all the Promises basics for Javascript.

You can find the original article on the [Othrys website](https://othrys.dev/author/axel/) and you can follow my [Twitter](https://twitter.com/ChandelierAxel) or tag me here to discuss about this article.