The async protocols mirror the sync ones with promises.
- Async iterable: has a
[Symbol.asyncIterator]()method. - Async iterator: has
next()(and optionallyreturn()andthrow()) that returns a promise for{ value, done }.
for await (const x of source) works like this (spec: GetIterator with kind async, plus the shared loop body):
- Look up
source[Symbol.asyncIterator]. If it is missing, fall back tosource[Symbol.iterator]and wrap the sync iterator in anAsyncFromSyncIterator, which awaits each value. That is whyfor await (const v of arrayOfPromises)yields resolved values. - Call
next()and await the result, then readdoneandvalue. - Run the body. Only then ask for the next one. Iterations are strictly sequential, so a slow body delays the next pull, which is natural backpressure.
- On
break,returnor a throw, callreturn()and await it (AsyncIteratorClose). A rejection fromnext()ends the loop with that error and does not callreturn().
It is only allowed where await is: async functions and module top level.
async function* is the easy way to write an async iterator. await and yield both work in the body. yield awaits its operand, and return does too. The generator object queues requests, so calling next() three times without waiting returns three promises that settle in order, and the body runs one step at a time.
Typical sources are paged APIs, ReadableStream, Node Readable streams, readline, and events.on. By default, leaving a Node Readable loop early destroys the stream (destroyOnReturn: false changes that).
Async iteration is not parallelism. For concurrency, start the work first and await the results (Promise.all).