An async iterable has a [Symbol.asyncIterator]() method whose iterator's next() returns a promise of { value, done } (the synchronous protocol is in Iterables and Iterators). for await...of, allowed in async functions and modules, awaits each step, and an async function* generator is the easiest way to produce one. It fits data that arrives in pieces: paginated APIs, streams, Node's readline and events.on().
async function* pages() { // like a paginated REST API
try {
for (let page = 1; ; page++) {
await new Promise(resolve => setTimeout(resolve, 10)); // simulated request
yield [`item${page}a`, `item${page}b`];
}
} finally { console.log("cleanup"); }
}
for await (const items of pages()) { console.log(items); if (items[0] === "item2a") break; }[ 'item1a', 'item1b' ] [ 'item2a', 'item2b' ] cleanup
Pages load only when the loop asks, and break calls the iterator's return(), so finally runs. Array.fromAsync(iterable) (ECMAScript 2026, in browsers since January 2024) collects a finite async iterable into an array one value at a time.