This post explains a quiz originally shared as a LinkedIn poll.
🔹 The Question
const ids = [1, 2, 3];
async function fetchData(id) {
return new Promise(resolve => {
setTimeout(() => resolve(id * 10), 100);
});
}
async function processAll() {
const results = [];
ids.forEach(async (id) => {
const data = await fetchData(id);
results.push(data);
});
console.log(results);
}
processAll();
Hint: Think about whether forEach waits for async callbacks to complete before continuing to the next statement.
🔹 Solution
Correct Answer: B) []
The output is: []
🧠 How this works
This is one of the most common async/await pitfalls in JavaScript. The issue is that forEach does not wait for async callbacks to complete. It simply fires off each callback and immediately moves on.
Here's what actually happens:
-
forEachis called with an async callback - For each element,
forEachinvokes the async callback - Each async callback returns a Promise immediately (without waiting for the
awaitinside) -
forEachignores these returned Promises entirely -
forEachcompletes synchronously, moving toconsole.log(results) - At this point, all three
fetchDatacalls are still pending -
console.log(results)prints[]because nothing has been pushed yet - ~100ms later, all three Promises resolve and push to
results, but nobody is watching anymore
The key insight: forEach was designed for synchronous operations. It doesn't understand Promises. When you pass an async function, it receives a Promise but does nothing with it—it doesn't await it.
🔍 Line-by-line explanation
const ids = [1, 2, 3]— creates an array of IDs to processfetchData(id)— simulates an async operation that takes 100ms and returnsid * 10-
processAll()is called:-
const results = []— creates empty results array -
ids.forEach(async (id) => {...})starts:-
Iteration 1: Calls
async (1) => {...}, gets Promise back, ignores it -
Iteration 2: Calls
async (2) => {...}, gets Promise back, ignores it -
Iteration 3: Calls
async (3) => {...}, gets Promise back, ignores it - All three iterations happen synchronously in microseconds
-
Iteration 1: Calls
-
forEachcompletes (it didn't wait for any Promises) -
console.log(results)→[](results is still empty)
-
-
~100ms later (after
console.logalready ran):- First Promise resolves, pushes
10to results - Second Promise resolves, pushes
20to results - Third Promise resolves, pushes
30to results -
resultsis now[10, 20, 30], but we already logged
- First Promise resolves, pushes
The misleading part: The async/await keywords make it look like each iteration will wait for fetchData to complete before moving on. But await only pauses execution within that async function—it doesn't pause forEach itself.
🔹 The Fix
Option 1: Use for...of for sequential processing
async function processAll() {
const results = [];
for (const id of ids) {
const data = await fetchData(id);
results.push(data);
}
console.log(results); // [10, 20, 30]
}
Option 2: Use Promise.all with map for parallel processing
async function processAll() {
const results = await Promise.all(
ids.map(id => fetchData(id))
);
console.log(results); // [10, 20, 30]
}
Option 3: Use for await...of with async iterables
async function processAll() {
const results = [];
for await (const data of ids.map(fetchData)) {
results.push(data);
}
console.log(results); // [10, 20, 30]
}
When to use which:
-
for...ofwithawait: When you need sequential processing (each iteration waits for the previous) -
Promise.allwithmap: When operations can run in parallel (faster, but all-or-nothing on errors) -
Promise.allSettled: When you want parallel execution but need to handle individual failures
🔹 Key Takeaways
-
forEachdoes not await async callbacks—it fires and forgets -
awaitonly pauses the inner async function, not the outerforEach - For sequential async iteration, use
for...ofwithawait - For parallel async iteration, use
Promise.allwithmap - This applies to other array methods too:
map,filter,reduceall ignore returned Promises - When you see
forEach(async ...), it's almost always a bug - ESLint rule
no-await-in-loopcan help catch this (though it flags the correctfor...ofpattern too)
Top comments (0)