DEV Community

Krishna Pravin
Krishna Pravin

Posted on • Edited on

for loop vs .map() for making multiple API calls

Promise / async-await is used for making API calls.

const response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`)
const todo = await response.json()
console.log(todo)
Enter fullscreen mode Exit fullscreen mode

Assuming I have a list of ids of todo items and I want the title of all them then I shall use the below snippet inside an async function

const todoIdList = [1, 2, 3, 4]
for (const id of todoIdList) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
  const todo = await response.json()
  console.log(todo.title)
}
Enter fullscreen mode Exit fullscreen mode

This same can be written with any of these for, for...in, for...of loops.

Assuming each API request arbitrarily takes 100ms exactly, the total time taken for getting the details of four todo items will have to be greater than 400ms if we use any of the above-mentioned loops.

This execution time can be drastically reduced by using .map().

const todoIdList = [1, 2, 3, 4]
await Promise.all(
  todoIdList.map(async (id) => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
    const todo = await response.json()
    console.log(todo.title)
  })
)
Enter fullscreen mode Exit fullscreen mode

Adding timers

const todoIdList = [1, 2, 3, 4]
console.time('for {}');
for (const id of todoIdList) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
  const todo = await response.json()
  console.log(todo.title)
}
console.timeEnd('for {}');

console.time('.map()');
await Promise.all(
  todoIdList.map(async (id) => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
    const todo = await response.json()
    console.log(todo.title)
  })
)
console.timeEnd('.map()');
Enter fullscreen mode Exit fullscreen mode

The Reason

for loop

for loop goes to the next iteration only after the whole block's execution is completed. In the above scenario only after both the promises(await) gets resolved, for loop moves to the next iteration and makes the API call for the next todo item.

.map()

.map() moves on to the next item as soon as a promise is returned. It does not wait until the promise is resolved. In the above scenario, .map() does not wait until the response for todo items comes from the server. It makes all the API calls one by one and for each API call it makes, a respective promise is returned. Promise.all waits until all of these promises are resolved.

async/await is syntactic sugar for Promises

It will be more clear if the same code is written without async/await

const todoIdList = [1, 2, 3, 4]
console.time('.map()')
Promise.all(
  todoIdList.map(id => {
    return new Promise((resolve) => {
      fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
        .then(response => {
          return new Promise(() => {
            response.json()
              .then(todo => {
                console.log(todo.title)
                resolve()
              })
          })
        })
    })
  })
)
.then(() => {
  console.timeEnd('.map()');
})
Enter fullscreen mode Exit fullscreen mode

It is not possible to mimic the respective code for for loop by replacing async/await with Promises because, the control which triggers the next iteration will have to written within the .then() block. This piece of code will have to be created within the JS engine.

All the snippets are working code, you can try it directly in the browser console.
Note:

  • snippets need to be enclosed within an async function except for the last one
  • use Axios or any other suitable library if fetch is not available.

Let me know if there is an even better and easy/short way of making API calls.

Also, do not forget to mention any mistakes I've made or tips, suggestions to improve this content.

Top comments (6)

Collapse
 
zaquwa profile image
Quinn

Not really a fair comparison. Map is creating a new array of promises then asynchronously executing them. To do this with a for loop you would do something like this:

const todoIdList = [1, 2, 3, 4]
const promiseList = []

for (const id of todoIdList) {
const response = fetch(https://jsonplaceholder.typicode.com/todos/${id})
promiseList.push(response.json())
}

const responses = Promise.all(promiseList)

Collapse
 
askrishnapravin profile image
Krishna Pravin

This approach looks good. Pushing the promises into an array within for loop will achieve concurrency.
But when we have a need for more than one await inside the block, it will not work.
In the above code, response.json() won't work because response is a promise, it won't have json() method.

Collapse
 
yogendra3236 profile image
Yogendra

This is quite great. But, could you tell me what is the optimum way to resolve multiple promises and get their statuses, as Promise.all() fails as soon as any of the Promise rejects? I heard of Promise.allSettled() but is only available in recent versions of ES.
Thanks!!

Collapse
 
askrishnapravin profile image
Krishna Pravin
Collapse
 
rajeshmoka22 profile image
Rajesh Moka

This is a great article. I had trouble understanding the last example without async await but with promises. I knew promise.all takes promises array as an argument, but why did we write promise in each fetch call? Can u explain a little

Collapse
 
askrishnapravin profile image
Krishna Pravin

The last example(using promise) is the same as the previous one(using await).

We have a promise inside fetch because parsing response as json response.json() returns a promise. For each API call, Promise.all() will first wait for the API call's response to arrive, and then it will wait for the json parsing to complete.

When Promise.all takes an array of promises, it will wait for all the inner promises as well to get resolved.