Go back
Don't use async and await inside forEach
The motivation for not using async and await on forEach?
I was working on a task related to adding some content to an index variable, and I was iterating
over an array using forEach. Inside the loop, I needed to get some content from each object and
push it to another array. I was getting some problems because even though I was able to see the content
in my console, the final array was not being populated as expected. It was something similar to this:
const finalArray: string[] = []
// ... some previous psuhes of text content in this array
finalArray.push('title of some content')
const items = [
{ id: 1, content: 'First item content' },
{ id: 2, content: 'Second item content' },
{ id: 3, content: 'Third item content' },
];
items.forEach(async (item) => {
const content = await fetchContent(item.id);
finalArray.push(content);
});
console.log(finalArray);We might expect something like this:
[
'title of some content',
'Content for item 1',
'Content for item 2',
'Content for item 3'
]
But instead we get:
[
'title of some content'
]You might be thinking: “Why this is happening? Even if i can see the content in the console, why the final array is not being populated?”
That’s what i was asking myself earlier and then I came across the solution
How can we fix/solve this?
I’m going to dig a little deeper into this issue later on this note, but for now, you have two options to solve this problem:
- Use a
for...ofloop instead offorEach. This allows you to useawaitproperly within the loop.
const finalArray: string[] = []
// ... some previous psuhes of text content in this array
finalArray.push('title of some content')
const items = [
{ id: 1, content: 'First item content' },
{ id: 2, content: 'Second item content' },
{ id: 3, content: 'Third item content' },
];
for (const item of items) {
const content = await fetchContent(item.id);
finalArray.push(content);
}
console.log(finalArray);
const fetchAllContent = async () => {
for (const item of items) {
const content = await fetchContent(item.id);
finalArray.push(content);
}
};
fetchAllContent();
// [
// 'title of some content',
// 'Content for item 1',
// 'Content for item 2',
// 'Content for item 3'
// ]- Use
Promise.allwithmapto handle the asynchronous operations concurrently.
const fetchContent = async (id: number): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => resolve(`Content for item ${id}`), 1000);
});
};
const finalArray: string[] = [];
finalArray.push("title of some content");
const items = [
{ id: 1, content: "First item content" },
{ id: 2, content: "Second item content" },
{ id: 3, content: "Third item content" },
];
Promise.all(
items.map(async (item) => {
const content = await fetchContent(item.id);
finalArray.push(content);
})
).then(() => {
console.log(finalArray);
}).catch((error) => {
console.error('Error fetching content:', error);
});The key difference here is that with the for...of loop, each iteration waits for the previous one to complete before moving on,
while the Promise.all approach runs all the fetch operations in parallel and waits for all of them to complete before proceeding.
There is also a third approach to this, but I’m not going to cover it here because I’m not so familiar to use it,
but just for the sake of completeness, you can also use reduce to chain promises sequentially.
You can read more about this approach here🔗
Why does this happen?
If you was just interested in the solution, you can stop reading here, but now we are going to explore why this happens
I believe sometimes it’s more important than solving a problem to understand why it happens in the first place.
The key issue with forEach and async/await
The main reason why using async and await inside a forEach loop doesn’t work as expected is that forEach does not handle asynchronous operations properly.
When you use forEach, it doesn’t wait for the promises returned by the async function to resolve before moving on to the next iteration. This means that all the
asynchronous operations are initiated almost simultaneously,
and the loop completes before any of the promises have resolved.
As a result, when you log the final array, it only contains the initial values,
as the asynchronous operations haven’t completed yet.
- The
forEachloop starts and creates multiple async functions (one for each item) - Each async function returns a Promise that gets added to the microtask queue
- The
forEachloop completes its synchronous iteration almost instantly - Your
console.logexecutes immediately after - Only then does the event loop process the microtask queue and resolve the Promises
- The
finalArray.push()operations happen, but too late - you’ve already logged the array
Conclusion
Well that’s it, I hope this note was helpful to you.
Here are some other resources/discussions related to this topic that you might find interesting:
- JavaScript: Why doesn’t async/await work inside forEach?🔗 -> Pretty cool concepts here
- Microstask🔗
And remember:
- Drink water
- Eat fruits
- Avoid deploys on Fridays
- Don’t compare yourself to others
- Get some rest and respect your time and limits :)