Why forEach Is Losing Favor: Switch to for...of for Async JavaScript Loops
The article explains why Array.prototype.forEach is increasingly discouraged in modern JavaScript, especially with async/await, and demonstrates how replacing it with a for...of loop resolves async handling, break/continue control, and improves code clarity.
If you follow popular open‑source projects, you’ll notice a trend: using Array.prototype.forEach is increasingly discouraged in code reviews, with a recommendation to replace it with a for...of loop. forEach was once the elegant functional‑programming tool of the ES5 era; why has it fallen out of favor today?
1. Async code pitfalls
The most common reason to abandon forEach is that, now that async/await is the standard for asynchronous programming, forEach feels out of place.
Problem: forEach cannot correctly handle await.
The callback of forEach runs in its own function scope and does not wait for any asynchronous operation. It executes all iterations synchronously without pausing for a Promise.
Typical erroneous example:
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const ids = [1, 2, 3];
async function processUsers(ids) {
console.log('开始处理...');
ids.forEach(async (id) => {
await sleep(1000); // expect 1‑second wait per iteration
console.log(`处理完用户 ${id}`);
});
console.log('...全部处理完毕');
}
processUsers(ids);
// Actual output (approx. 1 s later, almost simultaneous):
// 开始处理...
// ...全部处理完毕
// 处理完用户 1
// 处理完用户 2
// 处理完用户 3From the result you can see that the "...全部处理完毕" log is printed immediately; forEach never waited for the internal await. It merely triggered three parallel sleep operations, which defeats the intention of sequential processing.
Solution: Use for...of.
The for...of loop is the perfect partner for await. It is a true loop construct, not a function call, so await can pause the entire loop until the Promise resolves.
For scenarios that require serial execution of asynchronous tasks, for...of is indispensable.
2. Cannot break or skip the loop
Another native limitation of forEach is the inability to use break or continue as you can with a traditional for loop.
Using return inside a forEach callback only exits that callback, behaving like continue, but it does not terminate the whole loop.
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(num => {
if (num === 3) {
// Want to stop the loop here, but cannot! return only skips this iteration
return;
}
console.log(num);
});
// Output: 1, 2, 4, 5To forcefully break, you would have to throw an exception, which is considered non‑elegant and is discouraged by most coding standards.
In contrast, for...of fully supports break, continue, and other flow‑control keywords, making the code logic clearer and more intuitive.
Of course, this does not mean forEach should be completely abandoned. In certain specific scenarios it still has its place:
When the operation is entirely synchronous and you do not need to break or skip, the chainable syntax of forEach remains elegant.
When combined with other array methods (e.g., filter, map), forEach can serve as a clean terminal step, preserving a consistent coding style.
Example of a synchronous use case:
['error', 'warning', 'info'].forEach(level => console.log(level));Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaScript
Provides JavaScript enthusiasts with tutorials and experience sharing on web front‑end technologies, including JavaScript, Node.js, Deno, Vue.js, React, Angular, HTML5, CSS3, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
