CONCEPT Cited by 1 source
Async iteration¶
Async iteration is the JavaScript language protocol for
consuming asynchronous sequences: an object implementing
Symbol.asyncIterator + next() returning a Promise<{ value,
done }>, consumed with for await (const x of iterable).
Shipped in ES2018 (two years after the WHATWG Streams
Standard was finalized, 2016).
The timing matters: Web streams' getReader() / read() /
{ value, done } / releaseLock() protocol is an
async-iteration analog built before the language had async
iteration. The same semantics — "give me the next value, tell
me when the sequence is done" — were coded as a class hierarchy
with locks and controllers because the syntactic primitive for
"awaiting the next item of a sequence" did not exist.
The protocol¶
// An async iterable implements Symbol.asyncIterator:
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
async next() {
if (i >= 10) return { done: true, value: undefined };
return { done: false, value: i++ };
},
};
},
};
// Consumption:
for await (const value of asyncIterable) {
console.log(value); // 0, 1, 2, …, 9
}
Async generators (async function*) are sugar producing
async iterables:
Why it matters to streaming APIs¶
-
Backpressure becomes implicit. The consumer pulling drives the producer; stopping iteration stops production. No advisory
desiredSizeto forget to check. See concepts/backpressure + concepts/pull-vs-push-streams. -
Cancellation becomes implicit.
breakout of thefor await…ofloop runs the iterator'sreturn()method, letting the producer clean up. Noreader.cancel()to forget; no lock to release. -
Composition is primitive. Async generators can await other async iterables; pipelines are syntactic (
yield */for await of). No separatepipeTo/pipeThroughclass-method surface needed. -
Error handling is
try/catch. Upstream exceptions propagate throughawait;try/finallyruns the iterator's cleanup. NoPromise-branch-per-error surface.
The retrofit gap¶
Web streams added async iteration support post-hoc —
ReadableStream[@@asyncIterator] exists. But:
"Async iteration was retrofitted onto an API that wasn't designed for it, and it shows. Features like [BYOB (bring your own buffer)] reads aren't accessible through iteration. The underlying complexity of readers, locks, and controllers are still there, just hidden. When something does go wrong, or when additional features of the API are needed, developers find themselves back in the weeds of the original API." — 2026-02-27 Cloudflare post
An API designed around async iteration from day one —
like systems/new-streams — can expose the contract
directly (AsyncIterable<Uint8Array[]>) with no class
hierarchy, no locks, no { value, done } returned from user
code. That's the design thesis of the POC.
Lazy by default¶
for await…of is lazy: the iterator's next() is called
only when the consumer is ready. A pipeline built as chained
async generators executes on demand:
// Each stage only runs when the next `for await` pulls
async function* compress(source) {
for await (const chunk of source) yield gzip(chunk);
}
async function* encrypt(source) {
for await (const chunk of source) yield aes(chunk);
}
for await (const c of encrypt(compress(readFile()))) {
// reads flow from disk only when this loop pulls
}
Contrast with push-based APIs (Web streams pipeThrough) that
eagerly pump data from source through transforms into internal
buffers regardless of downstream consumption.
Performance note¶
Per-iteration await on a Promise<{ value, done }> is not
free — the promise allocation and microtask scheduling cost
accumulates in hot paths (see
concepts/promise-allocation-overhead). The
systems/new-streams answer is to batch: yield
Uint8Array[] per iteration instead of one chunk, amortizing
the per-iteration cost across multiple chunks.
Seen in¶
- sources/2026-02-27-cloudflare-a-better-streams-api-is-possible-for-javascript
— canonical wiki instance: async iteration's 2016-vs-2018
timing mismatch argued as the root design flaw in Web
streams;
AsyncIterable<Uint8Array[]>proposed as the natural streaming-API substrate.
Related¶
- concepts/backpressure — async iteration makes backpressure implicit (pull-based).
- concepts/pull-vs-push-streams — the axis async iteration is the canonical pull-based JS primitive on.
- concepts/promise-allocation-overhead — the per-await cost that motivates batched-chunk designs.
- systems/web-streams-api — the API whose 2014-2016 design predated async iteration, requiring retrofit.
- systems/new-streams — API designed around async iteration from day one.