Async Iterators vs Generators: Mastering Asynchronous Streams


JavaScript gives us powerful tools for managing sequences of data whether those sequences are finite, infinite, synchronous, or asynchronous. Among the most important of these tools are generators and async iterators.
At first glance, they may look similar. Both use the yield
keyword and can be looped over. But they solve different problems and shine in different contexts.
In this article, we’ll dive deep into both concepts, understand how they work, and highlight their nuanced differences with clear examples.
What Are Generators?
Generators are special functions that can be paused and resumed. This makes them great for producing data lazily only when it’s needed.
Syntax
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}
Note the *
after the function
keyword it defines a generator. Calling generatorFunction()
doesn’t run the function immediately; instead, it returns an iterator that we can call .next()
on to get values one by one.
Example: Basic Generator
function* countToThree() {
yield 1;
yield 2;
yield 3;
}
const counter = countToThree();
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: 3, done: false }
console.log(counter.next()); // { value: undefined, done: true }
Use Case: When to Use Generators
Generators are ideal when you want to:
Work with large or infinite sequences.
Implement lazy evaluation (e.g., pagination).
Pause and resume logic with shared state.
What Are Async Iterators?
Async iterators extend this concept to the asynchronous world, allowing you to await
each value in a loop. They're useful when data comes in over time such as from a stream, socket, or API.
Syntax
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
}
You use them with a for await...of
loop:
(async () => {
for await (const value of asyncGenerator()) {
console.log(value);
}
})();
Example: Simulating Data Stream
async function* fetchStream() {
const data = [1, 2, 3];
for (const item of data) {
await new Promise(resolve => setTimeout(resolve, 1000)); // simulate delay
yield item;
}
}
(async () => {
for await (const item of fetchStream()) {
console.log('Received:', item);
}
})();
In this example, one item is yielded every second, simulating data coming in over time (as in a network stream or file read).
Code Comparison: Generator vs Async Iterator
Here’s a quick side-by-side view of similar logic:
Feature | Generator | Async Iterator |
Declaration | function* | async function* |
Loop style | for...of | for await...of |
Handles async | ❌ No | ✅ Yes |
Yield pauses | ✅ Yes | ✅ Yes (and can await ) |
Common use case | Lazy evaluation, state machines | Data streaming, async sources |
Key Differences
Synchronous vs Asynchronous
Generators are synchronous: values are yielded immediately.
Async iterators allow
await
inside the function, enabling use with asynchronous data (e.g., fetch, streams).
Usage in Loops
Generators work with
for...of
.Async iterators must be consumed with
for await...of
.
Return Type
Generators return an iterator object with
.next()
.Async iterators return a promise-wrapped iterator (
.next()
returns a Promise).
Use Cases
Use generators when values are available synchronously or from CPU-bound tasks.
Use async iterators for I/O-bound operations like network calls, file reads, or user interaction streams.
When to Use Which?
Use Generators if your values are ready to go synchronously and you just want control over when they're delivered.
Use Async Iterators when you're dealing with delayed data, such as streaming APIs, file reads, or any situation where values come in over time.
Conclusion
Generators and async iterators are two sides of the same coin each designed for handling sequences, but one is built for the synchronous world and the other for the asynchronous.
Understanding the subtle but critical differences between them helps you write cleaner, more efficient, and more expressive code—especially in complex applications dealing with data streams or lazy loading.
Subscribe to my newsletter
Read articles from Peter Bamigboye directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Peter Bamigboye
Peter Bamigboye
I am Peter, a front-end web developer who writes on current technologies that I'm learning or technologies that I feel the need to simplify.