Async iterators and for-await-of let you loop over a sequence of values that arrive over time — like paginated API results or streamed data — with the same simple syntax as a regular for-of loop.
1Async Iterators | JavaScript Tutorial - In-Depth Guide Part 1
'for await...of' loops over an async iterable, automatically awaiting each value before the loop body runs — perfect for sequences that arrive over time.
async function processAll(asyncIterable) {
for await (const value of asyncIterable) {
console.log(value);
}
}for await...of
2Async Iterators | JavaScript Tutorial - In-Depth Guide Part 2
An async generator function (marked with both 'async' and '*') can produce values one at a time using 'yield', each of which for-await-of will await automatically.
async function* fetchAllPages(url) {
let next = url;
while (next) {
const res = await fetch(next);
const page = await res.json();
yield page.items;
next = page.nextPageUrl;
}
}Async Generators
3Async Iterators | JavaScript Tutorial - In-Depth Guide Part 3
Consuming a paginated API becomes a simple loop, hiding all the pagination bookkeeping inside the async generator itself.
for await (const items of fetchAllPages('/api/items?page=1')) {
items.forEach(processItem);
}Simplifying Pagination
4Async Iterators | JavaScript Tutorial - In-Depth Guide Part 4
Async iteration is fundamentally different from awaiting an array of promises: values are processed one at a time, in order, as each becomes ready — not all concurrently.
// Sequential, one at a time:
for await (const page of fetchAllPages(url)) { /* ... */ }
// vs concurrent, all at once:
await Promise.all([fetchPage(1), fetchPage(2), fetchPage(3)]);Sequential, Not Concurrent
5Async Iterators | JavaScript Tutorial - In-Depth Guide Part 5
Modern Web APIs like the Fetch Response body and Node.js streams implement the async iterable protocol natively, so you can loop over them directly with for-await-of.
const response = await fetch('/large-file');
for await (const chunk of response.body) {
processChunk(chunk);
}Native Async Iterables
6Step-by-Step Breakdown
'for await...of' loops over an async iterable, automatically awaiting each value before the loop body runs — perfect for sequences that arrive over time.
An async generator function (marked with both 'async' and '*') can produce values one at a time using 'yield', each of which for-await-of will await automatically.
Checkpoint: What two keywords mark a function as an async generator?
- →
asyncand* - →
yieldand*
Consuming a paginated API becomes a simple loop, hiding all the pagination bookkeeping inside the async generator itself.
Async iteration is fundamentally different from awaiting an array of promises: values are processed one at a time, in order, as each becomes ready — not all concurrently.
Checkpoint: Does for await...of process values concurrently, like Promise.all() does?
- →Yes, it awaits every value at the same time
- →No, it processes them one at a time, in sequence
Modern Web APIs like the Fetch Response body and Node.js streams implement the async iterable protocol natively, so you can loop over them directly with for-await-of.
Next, we'll explore 'The Clipboard API'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Announce Progress Incrementally While Streaming Large Datasets
When using for-await-of to process a large streamed dataset that affects the UI (like a progressively-loading table), update an ARIA live region periodically (not on every single item) to keep assistive technology users informed of loading progress without overwhelming them with rapid announcements.
SEO Implications
- 1
Streaming Large Payloads Can Improve Perceived Performance
Processing streamed or paginated data incrementally with async iteration, rather than waiting for an entire large payload to load before rendering anything, can improve perceived load speed and related Core Web Vitals like Largest Contentful Paint.
Best Practices
Use Async Generators to Hide Pagination Complexity from Consumers
Wrapping page-fetching logic in an async generator lets consuming code loop over "all items" without knowing or caring about page numbers, cursors, or API-specific pagination details.
Choose for-await-of for Genuinely Sequential Data, Promise.all() for Independent Parallel Work
Async iteration processes one value at a time by design; reaching for it when operations are actually independent and could run concurrently leaves performance on the table.
Frequent Bugs
Using for-await-of over an array of independent promises, unintentionally processing them one at a time sequentially instead of concurrently.
If the promises are independent and could run in parallel, use Promise.all() (or allSettled) instead of for-await-of, which is designed for genuinely sequential async sequences.
Forgetting the `async` keyword on a generator function definition (writing just `function*` when async work is needed inside), causing yielded promises to appear as unresolved Promise objects instead of their awaited values.
Ensure the generator function is declared with both `async` and `*` (`async function*`) whenever it needs to await something before yielding.
Real-World Examples
Streaming and Processing a Large CSV Export
A data export feature needed to process a very large paginated dataset from an API without loading every page into memory at once.
async function* fetchAllRows(endpoint) {
let cursor = null;
do {
const res = await fetch(`${endpoint}?cursor=${cursor ?? ''}`);
const { rows, nextCursor } = await res.json();
yield* rows;
cursor = nextCursor;
} while (cursor);
}
for await (const row of fetchAllRows('/api/export')) {
writeRowToFile(row);
}