Walking an array to do something with each item, writing a log line, sending a message, refreshing the display: that is the one and only use of forEach.
It builds nothing and returns nothing. That is both its simplicity and its limit.
Definition
forEach is an Array method that calls a function on each item, in order, and hands back undefined. Whatever the function returns is ignored.
const items = ["keyboard", "mouse"];
const result = items.forEach((a, i) => console.log(i, a));
// 0 keyboard
// 1 mouse
console.log(result); // undefinedThat return value rules out any chaining. Writing items.forEach(...).filter(...) throws a TypeError, since the method is then called on undefined. A chain of steps therefore goes through Array.map() and Array.filter().
What it cannot do
break and continue mean nothing inside it: they belong to loops, not to functions. A return in the body only leaves the current pass, exactly like a continue.
// Leaving early calls for a real loop
for (const n of [1, 2, 3]) {
if (n === 2) break;
console.log(n); // 1
}When stopping early is precisely the goal, two methods do it on their own: Array.some() halts on the first success, Array.find() hands back the item found and ends the walk.
The waiting trap
An async function returns a Promise, and forEach throws away whatever the function returns. So it never waits for it.
const wait = (ms) => new Promise((ok) => setTimeout(ok, ms));
async function send(ids) {
ids.forEach(async (id) => {
await wait(10);
console.log("sent", id);
});
console.log("done"); // prints BEFORE the sends
}The "done" line comes out first, and an error raised inside a send travels nowhere. To wait for real, use an of loop with await, or Promise.all over a Array.map() when the sends are independent.
Frequently asked questions
forEach or for...of?
The for...of loop accepts break, continue and await, which makes it the safest default. forEach keeps the edge in brevity on a short action, inside a chain where a functional style is worth holding.
Does it visit the empty slots of an array?
No, it skips them. On [1, , 3] the function is called at positions 0 and 2 only, and the middle slot is ignored without any warning. Such sparse arrays are rare but appear after a delete or an assignment past the end.
Can the array be changed during the walk?
Technically yes, but the number of passes is fixed at the start: items appended along the way are never visited, and a removal shifts the following ones, which then get skipped. Build a new array instead.