Yeda AI Tips · #116

Español

forEach Doesn't Await Anything

An async callback inside forEach does not wait for anything. Your loop finishes before the work does — and because the code looks right, this is one of the most common async bugs an AI assistant will happily write for you.

Why forEach can't wait

MDN says it plainly: "forEach() expects a synchronous function — it does not wait for promises." An async function always returns a promise, but forEach throws that return value away. It calls your callback once per element, back to back, and moves on. The result:

const ratings = [5, 4, 5];
let sum = 0;

ratings.forEach(async (rating) => {
  sum = await sum + rating;
});

console.log(sum); // 0 — not 14

Every callback is started; none is finished before the console.log runs. Worse failure modes follow the same shape: DB writes that land after the response is sent, files closed while still being written, try/catch around the loop that catches nothing because rejections escape as unhandled promise rejections.

Pick your intent: two patterns that actually wait

The fix is not a clever wrapper around forEach — it's stating which behavior you want.

Sequential — order and back-pressure matter. Use for...of with await inside. Each iteration finishes before the next starts, errors surface at the await, and you never fire 10,000 requests at once:

for (const user of users) {
  await sendEmail(user); // one at a time, in order
}

Concurrent — order doesn't matter. Use Promise.all over a map. All the work starts immediately, and the surrounding function pauses until every promise settles:

const results = await Promise.all(users.map((u) => fetchProfile(u.id)));

Promise.all is fail-fast: it rejects as soon as any input promise rejects, with that first error. When you need every outcome even if some fail, use Promise.allSettled, which always waits and returns {status, value|reason} per item.

Rules of thumb

SituationUseWhy
Order matters, or each step depends on the lastfor...of + awaitStrictly sequential, natural error handling
Independent work, want it fast, one failure should abortawait Promise.all(items.map(fn))Concurrent, fail-fast
Independent work, need every result even on failuresawait Promise.allSettled(items.map(fn))Never short-circuits
Fire-and-forget side effects on a sync valueforEach (non-async callback)Fine — nothing to await
Any async callbacknever forEachThe promise is discarded

Power-user notes

Resources

Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.

Talk to us · Read the blog