Async loop in Javascript

In Javascript, there are so many ways to loop through array elements. One of the everyday use cases is sending API request from an array of object. There are two ways to create an async loop in Javascript. Each has its pros and cons. This blog post will go through these two ways. An example of an array element:

// Fetch all information of these sites
const sourceAPI = [
  "https://google.com",
  "https://duckduckgo.com",
  "https://msn.com"
];

Using For loop

The first way is using traditional for, while loop in Javascript. For loop is a naive solution, but it works for most of the use case. At run time, the browser iterates through an array of elements and wait for the Promise to resolve before continuing to create another Promise for the following element. The ugly illustration might help you to understand what happens at run time

Illustration photo

There are a few benefits of using this way. The first one is that you can easily handle each of the failed requests individually. This could be handy to record any failed requests and start a retry. Furthermore, for loops is easier to understand for any newbie to the language. It will require less understanding of Javascript to understand the intention of the code.

async function fetchAllSource(sources) {
  const results = [];
  for (let currentFetchIndex = 0; currentFetchIndex < sources.length; currentFetchIndex++) {
    const fetchedResult = await fetch(sources[currentFetchIndex]);
    results.push(fethcedResult);
  }
  return results
}

One of the drawbacks of this solution is the function's performance when the number of elements is significant. The reason is that the loop has to loop through and wait for every Promise to resolve sequentially. Therefore, this method is not an idea for a large array.


Using Promise.all

Javascript supports the functional paradigm. This is powerful as it reduces any unintended mutation. People would think that we could combine async and map function like below

async function fetchAllSource(sources) {
  return sources.map(async (source) => {
    const fetchResult = await fetch(source);
    return fetchResult;
  })
}

The code snippet above does not work because the map function is synchronous. It means the map function will exit and return before the Promise resolve. To make the map function wait for the Promise to resolve, we can use Promise.all function. From the documentation, the function return a Promise that will resolve when all of the Promise array resolved. So, we can rewrite the function above like this.

async function fetchAllSource(sources) {
  return await Promise.all(sources.map(async (source) => {
    const fetchResult = await fetch(source);
    return fetchResult;
  }))
}

The benefit of using this implementation is performance. All async is being called in nearly parallel.


So the promise will get resolved when the longest fetch request gets resolved. Another benefit of using map function is that we can avoid mutate the result array like the first solution. This will prevent any side effect.

However, there are two downsides to using Promise.all or any other Promise functions. The first one is that it would be harder for the newbie or other team member to understand. The reason is that other programmers reading the code may or may not know about the functional programming paradigm and what the Promise function is. Other drawbacks of using the Promise function is memory consumption. Each array element gets allocated memory. The function will have to allocate much memory for a significant array, whereas the for loop solution will reuse the stack and memory.

To sum up, both methods work well to fetch a large amount of data from an array. Each has pros and cons, so use it wisely.

© 2021 Minh. Made with ❤️ using Gatsby and W3layouts