Does “for…of” loop wait for async awaits in JavaScript?

Worth a direct correction before anything else: a `for…of` loop does wait for an `await` inside it — each iteration pauses until the awaited promise resolves before the loop advances to the next one. That’s the opposite of what a stub answer to this question sometimes claims, and it’s easy to get backwards since the similarly-named `forEach` genuinely doesn’t wait, which is the actual source of confusion this question is usually really asking about.

In this post: for…of with await runs sequentially · forEach genuinely doesn’t wait · Running things in parallel instead · Don’t confuse this with for await…of · Which one should you actually use? · Related reading


for…of with await runs sequentially
const fetchData = (id) =>
    new Promise((resolve) => setTimeout(() => resolve(`Data for ${id}`), 1000));

const processItems = async () => {
    const items = [1, 2, 3];
    for (const item of items) {
        console.log(`Processing ${item}`);
        const data = await fetchData(item);
        console.log(data);
    }
    console.log("Done!");
};

processItems();
// Logs: Processing 1, Data for 1, Processing 2, Data for 2, Processing 3, Data for 3, Done!
// Total time: ~3 seconds -- each item genuinely waits for the previous one

This works because a regular `for…of` loop is just ordinary synchronous control flow — `await` inside its body pauses the enclosing `async` function at that exact point, the same way it would anywhere else in the function. The loop itself has no special async behavior; it’s the `await` statement doing the waiting, and the loop simply doesn’t advance to the next iteration until the current one’s function execution resumes.

Worth handling errors explicitly rather than letting one failed item abort the whole loop — an unhandled rejection from `await fetchData(item)` throws out of the loop entirely, skipping every remaining item. Wrapping the awaited call in its own try/catch keeps one failure from taking down the rest of the run, the same discipline worth applying to any sequential batch of awaited operations:

for (const item of items) {
    try {
        const data = await fetchData(item);
        console.log(data);
    } catch (err) {
        console.error(`Item ${item} failed:`, err);
    }
}

for…of with await genuinely waits, sequentially, one iteration at a time. forEach with await genuinely doesn’t — it fires every callback immediately and ignores whatever each one returns.

forEach genuinely doesn’t wait

This is the real, correct version of the claim the intro corrected above — it just applies to `forEach`, not `for…of`:

items.forEach(async (item) => {
    console.log(`Processing ${item}`);
    await fetchData(item); // forEach never looks at this promise at all
});
console.log("Done!"); // Logs immediately -- before any fetchData call resolves

`Array.prototype.forEach` was designed before `async`/`await` existed and has no awareness of promises — it calls the callback for every item and immediately moves to the next one, completely ignoring whatever the callback returns, including a promise. Each `fetchData` call does still run, but all three start firing off essentially at once, and the surrounding code has no way to know when (or whether) any of them finished, since `forEach` itself returns `undefined`, not a promise to await.


Running things in parallel instead

If the operations genuinely don’t depend on each other — fetching three independent records, not something where item 2 needs item 1’s result — sequential `for…of` processing is needlessly slow. `Promise.all` starts every operation immediately and waits for all of them together:

const processItemsInParallel = async (items) => {
    const results = await Promise.all(items.map((item) => fetchData(item)));
    console.log(results); // ~1 second total, not ~3 -- all three ran concurrently
};

Worth knowing the real tradeoff before defaulting to this everywhere: `Promise.all` rejects as soon as any one of the promises rejects, discarding the results of everything else that may have already succeeded. Where partial success is actually useful — three independent API calls where one failing shouldn’t throw away the other two’s results — `Promise.allSettled` is the correct tool instead, since it always resolves with a per-item status (`fulfilled` or `rejected`) rather than short-circuiting on the first failure.


Don’t confuse this with for await…of

Worth knowing this is a real, separate piece of syntax, not a typo of the pattern above: `for await…of` is a distinct loop construct for iterating over async iterables — values that arrive over time, like a stream or a paginated API response exposed as an async generator — rather than a regular array or synchronous iterable that happens to contain awaited calls in its body:

async function* streamValues() {
    yield await fetchData(1);
    yield await fetchData(2);
}

for await (const value of streamValues()) {
    console.log(value); // Each value logged as it becomes available
}

Regular `for…of` with an `await` in the body (the pattern this post is mainly about) works on ordinary arrays and needs no special generator setup at all — `for await…of` is specifically for consuming something that’s already async-iterable by design. Reaching for `for await…of` on a plain array is unnecessary; it works, but it’s solving a problem the simpler pattern already handles.


Which one should you actually use?
  • Each item’s processing depends on the result of the previous one, or order matters — `for…of` with `await`, accepting the sequential cost.
  • The items are genuinely independent and speed matters — `Promise.all` (all-or-nothing) or `Promise.allSettled` (partial success is acceptable).
  • You reached for `await` inside `forEach` — that’s almost always a mistake; replace it with `for…of` if the intent was sequential, or `Promise.all`/`.map()` if the intent was parallel.
  • You’re consuming a genuine async iterable (a stream, a paginated generator) — `for await…of`, not a workaround built on the patterns above.


The short answer holds: `for…of` respects `await`, `forEach` doesn’t. The part worth internalizing beyond that one fact is picking sequential vs. parallel deliberately based on whether the operations actually depend on each other, rather than defaulting to whichever pattern happens to be muscle memory — and checking, specifically, that `await` never ends up inside a `forEach` callback by accident, since that’s the single most common version of this mistake actually showing up in real code.

App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *