Async generator
An async generator (async function*) is a function that hands out asynchronous values step by step. It creates an async iterator whose .next() method returns a Promise rather than a plain { value, done } object.
Theory
TL;DR
- Declaration:
async function* name() { ... }, that isasyncplus an asterisk. - It returns an async iterator;
.next()gives you aPromise<{ value, done }>. - Both
awaitandyieldare allowed inside, so you can run requests, pauses or database reads between steps. - It is iterated only with
for await...of; a plainfor...ofdoes not work, because it implements[Symbol.asyncIterator]. - It supports delegation with
yield*, plus the control methods.return()and.throw(). - Typical uses: data streaming, paginated APIs, lazy loading, asynchronous pipelines.
Quick example
async function* fetchNumbers() {
yield 1;
yield 2;
yield 3;
}
(async () => {
for await (const n of fetchNumbers()) {
console.log(n);
}
})();Output:
1
2
3The difference from a regular function*: every value is returned asynchronously, through a Promise, so the only way to consume it is for await...of.
Async yield: waiting between steps
Async generators earn their keep exactly when there are asynchronous actions between iterations: fetch, await, setTimeout, database queries and so on.
async function* delayedNumbers() {
for (let i = 1; i <= 3; i++) {
await new Promise(resolve => setTimeout(resolve, 1000)); // wait 1 second
yield i;
}
}
(async () => {
for await (const n of delayedNumbers()) {
console.log(n);
}
})();Output (with a one second pause between the numbers):
1
2
3Here the generator waits asynchronously inside await and then hands out the next value with yield. On every iteration for await...of implicitly awaits the result of .next().
What an async generator returns
If you crank the iterator by hand, every .next() has to be awaited:
const gen = delayedNumbers();
console.log(await gen.next()); // { value: 1, done: false }
console.log(await gen.next()); // { value: 2, done: false }
console.log(await gen.next()); // { value: 3, done: false }
console.log(await gen.next()); // { value: undefined, done: true }
gen.next()returns aPromisethat resolves to{ value, done }.
An async generator returns an object with the method:
[Symbol.asyncIterator]()which returns itself. Such an object can be iterated only with for await...of, never with for...of.
Just like a regular generator, an async one supports delegation with yield*, that is nesting another async generator:
async function* sub() {
yield 1;
yield 2;
}
async function* main() {
yield* sub(); // delegates the iteration
yield 3;
}
for await (const v of main()) {
console.log(v);
}Output:
1
2
3The control methods .return() and .throw() work as well:
async function* gen() {
try {
yield 1;
yield 2;
} catch (e) {
console.log('Error inside:', e.message);
} finally {
console.log('Generator finished');
}
}
const iterator = gen();
console.log(await iterator.next()); // { value: 1, done: false }
console.log(await iterator.throw(new Error("fail")));
// Error inside: fail
// Generator finishedData streams and async pipelines
Async generators are a perfect fit for streaming data processing:
async function* streamData(urls) {
for (const url of urls) {
const res = await fetch(url);
const data = await res.json();
yield data;
}
}
(async () => {
const urls = [
'/api/user',
'/api/posts',
'/api/comments'
];
for await (const chunk of streamData(urls)) {
console.log('Data:', chunk);
}
})();Here each fetch() runs in turn, and after every yield the data is handed out into the for await...of loop.
Several generators can be nested into one another like links of a conveyor:
async function* generateNumbers() {
for (let i = 1; i <= 10; i++) {
await new Promise(r => setTimeout(r, 200));
yield i;
}
}
async function* filterEven(source) {
for await (const n of source) {
if (n % 2 === 0) yield n;
}
}
async function* double(source) {
for await (const n of source) {
yield n * 2;
}
}
// Combining several async generators:
(async () => {
const pipeline = double(filterEven(generateNumbers()));
for await (const value of pipeline) {
console.log(value); // 4, 8, 12, 16, 20
}
})();This is lazy asynchronous data processing, essentially a stream pipeline: nothing is computed up front, and every link pulls the next value only when it is asked for.
Where it is used, and a summary
| Scenario | How it is used |
|---|---|
| Data streaming | Gradually loading parts of a file or of a server response |
| Incremental requests | Working with an API that returns data in "pages" |
| Lazy computation | Producing values as they are needed |
| Async pipelines | Combining several data sources |
| Node.js Streams | Compatible with the ReadableStream and AsyncIterator interfaces |
Summary:
| Feature | Description |
|---|---|
| Declaration | async function* name() { ... } |
| Returns | An async iterator |
The .next() method | Returns Promise<{ value, done }> |
| Used with | for await...of |
yield | Hands out intermediate values asynchronously |
| Applications | Data streams, paginated APIs, lazy loading, event generation |
The main idea in one line: function* gives synchronous sequences, while async function* gives asynchronous ones, for example "one request at a time".
Common mistakes
- Iterating an async generator with
for...of. That throws, because the object implements[Symbol.asyncIterator], not[Symbol.iterator]; you needfor await...of. - Forgetting
awaitbefore.next(). Without it you get aPromise, not{ value, done }, and thedonecheck is alwaysundefined. - Using
for await...ofoutside an async context. It only works inside anasyncfunction or in a module that supports top-levelawait. - Expecting parallelism. An async generator is sequential: the next
fetchstarts only after the previous value has been taken. For parallel requests you needPromise.all, not a generator. - Putting
yieldinsideforEachor another callback.yieldonly works in the body of the generator itself, so use plainfororfor await...ofloops. - Not closing the generator. If you leave the loop with
breakorreturn, the engine calls.return(), so resources such as connections and streams should be released in afinallyblock.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.