skip to content

Async Generators

async function* lets you await inside a generator and yield results as they arrive — the natural shape for paginated APIs or streamed chunks. Interviewers use it to test whether you can produce lazily instead of collecting everything first.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In JavaScript, what does declaring a function as `async function*` let you do that a plain `function*` cannot, and what do you get back when you call `.next()` on the object it returns?

level: juniorimportance: should knowfreq 38%

answer

  1. await and yield in the same body
  2. calling it runs nothing
  3. the result record is wrapped
  4. the whole {value, done} is inside a Promise
  5. async iterator protocol: next, return, throw

basics

~20 s

async function* combines await and yield in one body: the function can wait for asynchronous work and then emit a value, repeatedly. Calling next() on the object it returns gives a Promise that fulfils with {value, done}, not that record directly.

solid answer

~40 s

A plain `function*` cannot contain `await` — that is a syntax error — so it can only produce values it already has. An `async function*` may use both `await` and `yield`, which makes it the natural shape for a producer that fetches or waits for each item before emitting it. Calling it does not run the body; it returns an **async generator object** implementing the async iterator protocol: `next()`, `return()`, `throw()`, and a `Symbol.asyncIterator` method returning itself. The crucial difference from a sync generator is that `next()` returns a *Promise* for `{ value, done }`, so `it.next().done` is `undefined` — you must await it. Yielded operands are implicitly awaited, so `yield somePromise` emits the resolved value. Consumers normally drive it with `for await...of`. This syntax is ES2018.

code

javascript · 10 lines
javascript
async function* doubled(values) {
  for (const v of values) {
    const resolved = await Promise.resolve(v);
    yield resolved * 2;
  }
}

const it = doubled([1, 2]);
console.log(it.next().done);           // undefined - next() returned a Promise
it.next().then((r) => console.log(r)); // { value: 4, done: false }

go deeper

for a junior

Be able to say that async function* allows await and yield together, that calling it returns an async generator object, and that next() gives you a Promise you must await before reading value or done.

for a middle

Explain the mechanics: no body code runs until the first next(), the yield operand is implicitly awaited, and the object exposes next/return/throw plus Symbol.asyncIterator returning itself.

for a senior

Show when the shape earns its place in production code: bounded memory over large sequences, first result available before the last, and the consumer able to stop the producer at any point.

for a principal

Own the API-design angle: whether a library surface should expose an async iterable, a callback stream, or a plain promise of an array, and what each choice commits your consumers to.

## Two suspension mechanisms in one function JavaScript has two ways for a function to pause. A generator (`function*`) pauses at `yield` and hands a value to whoever pulls the next one; it can emit many values, but it has no way to wait — writing `await` inside a `function*` body is a syntax error. An `async function` can pause at `await` while a promise settles, but it produces exactly one result: the single promise it returns. `async function*` is the union of the two. Its body may use `await` to wait for asynchronous work and `yield` to emit a value, as many times as it likes. That is precisely the shape of a producer that has to *ask* for each batch of data — a paginated API, a polling loop, a chunked read — and wants to hand items out as they arrive instead of collecting everything first. ```js async function* doubled(values) { for (const v of values) { const resolved = await Promise.resolve(v); // legal only in an async generator yield resolved * 2; } } ``` ## What calling it returns Calling an async generator function runs none of the body — exactly like a sync generator. It returns an **async generator object**. That object implements the async iterator protocol: - `next(v)` — resume the body, returning a promise for the next result; - `return(v)` — ask the body to finish early, as if a `return` executed at the suspended `yield`; - `throw(e)` — inject an exception at the suspended `yield`; - `[Symbol.asyncIterator]()` — returns the object itself, which is what makes it directly usable with `for await...of`. ## The promise-wrapped result record A sync generator's `next()` returns the record `{ value, done }` immediately. An async generator's `next()` returns a **Promise** that fulfils with that record. The whole record is inside the promise — not just the value: ```js const it = doubled([1, 2]); console.log(it.next().done); // undefined — next() gave you a Promise it.next().then((r) => console.log(r)); // { value: 4, done: false } ``` Reading `.value` or `.done` off the unawaited call is the single most common beginner bug here, and it fails silently: you get `undefined`, not an error, so a `while (!it.next().done)` loop spins forever. ## Yielded values are awaited for you Inside an async generator, the operand of `yield` is implicitly awaited before it is delivered. `yield fetchThing()` therefore emits the *resolved* value, and if that promise rejects, the rejection surfaces as a rejected `next()` promise rather than as a promise handed to the consumer. This differs from a sync generator, where yielding a promise hands the promise object itself out and leaves unwrapping to the consumer. ## Return values and completion `return x` in the body produces `{ value: x, done: true }` on the promise for that resumption. As with sync generators, a `for await...of` loop discards that final value — if the total or a summary matters, yield it as a regular item or expose it another way. ## Consuming one The everyday consumer is `for await...of`, which calls `next()`, awaits it, and stops when `done` is `true`: ```js for await (const n of doubled([1, 2])) console.log(n); // 2, then 4 ``` You can also drive it by hand with `await it.next()` in a loop, which is what you do when you need to interleave other logic between pulls. ## Why the shape matters Because the body only advances when someone asks for the next value, an async generator is *lazy*: the fetch for item two does not happen until the consumer has taken item one. That is what makes it a good fit for large or unbounded sequences — memory stays bounded to whatever one step holds, and the consumer can stop at any point without the producer having done the remaining work. ## Version note Async generators, the async iterator protocol, and `for await...of` are all ES2018 features and are available in every current browser and Node release; no transpilation or flag is needed on a modern baseline.

  • What happens if you write `await` inside a plain `function*` body?
    It is a syntax error in an ordinary generator: `await` is only a keyword inside async function bodies, including `async function*`. In a non-async generator, `await` is parsed as a plain identifier in sloppy mode and is a reserved word inside modules, so the code either misbehaves or fails to parse. The fix is to declare the generator `async function*`.
  • If an async generator yields a promise, what does the consumer receive?
    The resolved value. The `yield` operand in an async generator is implicitly awaited before the result record is produced, so `yield fetchThing()` emits the settled value rather than the promise object. If that promise rejects, the rejection is delivered by rejecting the `next()` promise, which surfaces as a throw at the consumer's `for await...of` loop.
  • Does calling an async generator function start any of its work?
    No. Like a sync generator, calling it only builds the generator object; the body does not begin executing until the first `next()` call. That means a network request written on the first line of the body has not been issued yet when the call returns, which matters if you were relying on it to warm up eagerly.

A plain generator is a vending machine already stocked with items; an async generator is a counter clerk who has to go into the back room for each order before handing it over.

saying these in an interview costs you the question

  • Says next() returns {value, done} directly
  • Checks it.next().done without awaiting it
  • Thinks await is allowed in a plain function*
  • Believes calling the function starts the body
  • Claims yielded promises reach the consumer unresolved

context

open as a page

An async generator throws — or awaits a promise that rejects — after having already yielded two values. Where does that error surface for the consumer, and can the generator still produce values afterwards?

level: middleimportance: should knowfreq 32%

basics

~20 s

The error rejects the promise returned by the next() call that resumed the body, so a for await...of loop throws at that iteration. The generator then moves to the completed state permanently: later next() calls fulfil with {value: undefined, done: true} rather than rethrowing.

open as a page

A function loops through every page of a paginated HTTP API, pushing each page's items into one array, and returns that array once the last page arrives. How would you restructure it as an async generator, and what does that change for the caller?

level: middleimportance: should knowfreq 48%

basics

~20 s

Declare it async function*, await each page inside the cursor loop, and yield the items instead of pushing them into an array. The caller then sees the first item before the last page is fetched and never holds the whole result set in memory.

open as a page

An async generator opens a database cursor before its first `yield` and closes it on the line after the yielding loop ends. If the consumer stops reading partway through, what actually runs inside the generator body, and how do you guarantee the cursor is closed?

level: seniorimportance: should knowfreq 34%

basics

~20 s

When a consumer stops early it calls the generator's return(), which resumes the body with a return completion at the suspended yield. Code after the loop never runs, so only a finally block around the yields closes the cursor — and abandoning the generator without return() runs nothing at all.

open as a page

You call `.next()` on an async generator a second time before the promise from the first `.next()` has settled. What does the async generator do with that second call, and what does that imply about prefetching the next item?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

The second call is queued. An async generator keeps an internal queue of pending requests and serves them strictly in order, never resuming its body reentrantly, so overlapping next() calls buy you no concurrency — the generator is serial by construction.

open as a page