Async #6 — Generators & Iterators

Symbol.iterator, generator functions, yield and yield*, custom iterables, range generators, and pausing function execution with .next().

11 min read
JavaScript
Async
Generators
Iterators

TABLE OF CONTENTS
Async #6 — Generators & Iterators

Iterators and generators let you define custom iteration behavior — for lazy sequences, infinite streams, and pausable functions.

Think of it like this:

  • An iterator is like a bookmark in a book — it remembers where you are and hands you the next page each time you ask. Each call to .next() turns one page.
  • A generator is like a recipe — you can pause after each step (yield), do something else, and resume right where you left off (next()). The ingredients and current step are preserved between pauses.

This article covers Symbol.iterator, generator functions, yield*, and patterns like range and take.

Prerequisites: Async #5 — async/await Under the Hood


1. The Iterator Protocol

Before the code, a quick distinction:

Iterable                      Iterator
"I can be looped over"        "I track WHERE you are in the loop"

Has [Symbol.iterator]()       Has next() → { value, done }
    ↓                              ↓
Returns an iterator            Each call moves forward

An object is an iterator if it has a next() method that returns { value, done }. An object is iterable if it has a [Symbol.iterator]() method that returns an iterator. for...of only works with iterables — it calls [Symbol.iterator]() under the hood to get an iterator, then calls .next() until done is true.

Loading editor...


2. Custom Iterable — Make an Object Work with for...of

Loading editor...


3. Generator Functions — function* and yield

A generator function returns a generator object that is BOTH iterable and an iterator. Each yield pauses execution — the function doesn't run to completion. Next .next() resumes from where it left off.

Loading editor...


4. yield* — Delegating to Another Iterable

yield* delegates to another iterable — it yields every value from that iterable one by one:

Loading editor...


5. Infinite Generators — Lazy Sequences

Generators are lazy — they only compute values when asked. This enables infinite sequences:

Loading editor...


6. Two-Way Communication — Passing Values INTO a Generator

This is the most mind-bending generator feature and the foundation of async/await. Data flows BOTH ways:

  • Outward: yield expression sends expression out via { value: expression, done: false }
  • Inward: .next(value) sends value back IN, and it becomes the result of the yield expression
gen.next(10)  ──→  [PAUSED AT yield]  → value goes IN
                   const a = yield "What is a?";
                                      ──→  "What is a?" goes OUT  →  caller gets { value: "What is a?", done: false }

.next(value) passes value back to where yield paused:

Loading editor...

This is what powers the async/await desugaring — the runner passes resolved promise values back via .next().


7. gen.throw() and gen.return()

Loading editor...


8. Practical Use — State Machines

Generators make excellent state machines because yield naturally represents a transition:

Loading editor...

No state variable, no switch/case — the generator body IS the state machine. Each yield is a state; each .next() is a transition.


Key Takeaways

  • Iterable = has [Symbol.iterator]() — works with for...of, spread, destructuring.
  • Iterator = has next() returning { value, done }.
  • Generator = function* that is BOTH iterable AND iterator — yield pauses, next() resumes.
  • yield* delegates to another iterable — cleaner than a for-loop over nested items.
  • Two-way: .next(value) passes data INTO the generator; .throw() and .return() control flow.
  • Generators are lazy — values are computed on demand, enabling infinite sequences.

Next: Async #7 — Async Iterationfor await...of, async generators, and async iterables.


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI