Async Iterators vs Generators: Mastering Asynchronous Streams

Peter BamigboyePeter Bamigboye
4 min read

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:

FeatureGeneratorAsync Iterator
Declarationfunction*async function*
Loop stylefor...offor await...of
Handles async❌ No✅ Yes
Yield pauses✅ Yes✅ Yes (and can await)
Common use caseLazy evaluation, state machinesData streaming, async sources

Key Differences

  1. 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).

  2. Usage in Loops

    • Generators work with for...of.

    • Async iterators must be consumed with for await...of.

  3. Return Type

    • Generators return an iterator object with .next().

    • Async iterators return a promise-wrapped iterator (.next() returns a Promise).

  4. 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.

2
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.