Async callbacks do not make forEach await returned promises. Choose sequential, parallel or bounded execution according to ordering and resource constraints.
Step-by-step walkthrough
Step 1: Choose required ordering
First decide whether operations may overlap and whether their completion order matters. Sequential execution is straightforward when each operation depends on the previous result. Independent work can overlap, but ordering of returned results and ordering of side effects are different concerns.
Step 2: Await the owned promises
For-of with await owns one operation at a time. Promise.all over map owns a set of promises and waits for the whole set. ForEach discards callback return values, so awaiting the forEach call does not await the asynchronous callback work.
Step 3: Bound resource use
Launching every operation can overwhelm connections or memory. Use a worker pool when the input is large, and define whether one failure stops scheduling or collects per-item outcomes. Already started work needs its own cancellation and cleanup policy.
Worked scenario
For-of with await serializes saves. Promise.all over map starts every save immediately and waits for completion.
async function saveSequentially(items, save) {
const results = [];
for (const item of items) results.push(await save(item));
return results;
}
async function saveInParallel(items, save) {
return Promise.all(items.map(item => save(item)));
}
// Both return results in input order; their start times differ.Walk through the example
The sequential loop starts the next save only after the current save fulfills. The parallel version starts all saves during map and then waits. Promise.all preserves result positions even when operations complete out of order, but external side effects may occur in any order.
Common mistake
Promise.all rejection does not cancel started work; excessive parallel requests can hit rate limits.
Verify the behavior
Use controlled promises to record start times and resolve operations out of order. Verify sequential work has only one active operation. Test one rejection in parallel and observe that other started operations still continue unless the adapter supports explicit cancellation.
Interview exercise
Upload with three active requests.
Answer and reasoning
Use a worker pool taking the next item after settlement; collect outcomes and handle cancellation deliberately.
Follow-up discussion
Does async make CPU work parallel? No: it describes promise-based control flow, not automatic worker execution. Why can an async forEach test pass accidentally? Fast operations may finish before assertions by chance; the caller still lacks a promise representing their completion.
Continue learning
Compare the scenario with the JavaScript interview questions and test your understanding with the JavaScript MCQs. For terminology and implementation details, consult the reference material.