Wait for promise in a forEach loop
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Array.prototype.forEach does not wait for async callbacks, so code after the loop can run before asynchronous work is finished. This leads to partial writes, empty results, or race conditions that are hard to debug. Use control flow patterns that are promise-aware, such as for...of with await or Promise.all for parallel work.
Why forEach and await Clash
forEach ignores return values from the callback, including promises. Even if the callback is marked async, the outer function does not pause for each iteration.
In this pattern, done length often prints before callbacks complete. The loop schedules work and exits immediately.
Correct Sequential Processing
Use for...of when each step depends on the previous one or when rate limits require strict ordering.
This output order matches input order and completes predictably.
Correct Parallel Processing
If iterations are independent, parallelize with Promise.all. This is usually faster and still easy to reason about.
Use Promise.allSettled when partial failure is acceptable and you need per-item status instead of fail-fast behavior.
Error Handling Strategy
Choose one error model and keep it consistent:
- Sequential plus
tryandcatchwhen each step can stop the workflow. - Parallel plus
Promise.allSettledwhen you want success and failure reports together. - Batched parallelism when full parallel load is too heavy for external APIs.
For production jobs, include item identifiers in errors so failed records can be retried safely.
Controlled Concurrency for Large Workloads
Running every promise at once is risky when arrays are large or external APIs enforce strict rate limits. A practical middle ground is limited parallelism, where you process items in small batches. This preserves throughput while reducing timeout and throttling errors.
This strategy is especially helpful for network-bound jobs such as webhook delivery, file metadata fetches, or API synchronization tasks.
When you review pull requests, check that async loops return or await the final promise chain. Silent fire-and-forget behavior is a common source of data loss in background jobs. Make async intent explicit in function names and comments so callers understand whether a function waits for completion or only schedules work.
Common Pitfalls
- Expecting
awaitinsideforEachto pause the outer function. - Using sequential loops for independent work and accepting avoidable latency.
- Using unbounded
Promise.allon huge arrays and overwhelming downstream services. - Ignoring error context, which makes retries and incident analysis difficult.
- Mixing sequential and parallel patterns in one function without clear intent.
Summary
forEachis not promise-aware and should not drive awaited control flow.- Use
for...ofwithawaitfor ordered sequential processing. - Use
Promise.allfor parallel independent tasks. - Choose explicit error behavior with
tryandcatchor settled results. - Keep concurrency strategy intentional to avoid races and throughput issues.

