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)
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)
}
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)
})
)
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()');
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()');
})
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 iffetch
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)
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)
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.
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!!
Shim:
npmjs.com/package/promise.allsettled
Polyfill:
logic24by7.com/promise-allsettled-...
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
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.