JavaScript Generators

Learn how generator functions pause and resume execution to build lazy sequences and iterables.

TL;DR

  1. 01Declare generators with function* to suspend and resume functions.
  2. 02Emit values lazily on demand using the yield keyword.
  3. 03Forward iteration sequences to external collections using yield* delegation.

Tips

  1. 01Use generators to compute massive data sequences lazily without consuming system memory up front.
  2. 02Assign generator methods to Symbol.iterator properties to create custom iterables cleanly.

Warnings

  1. 01Catch exceptions thrown inside generator scopes to prevent them from closing the iterator permanently.
  2. 02Avoid using spread syntax on infinite generators to prevent crashing the browser thread.

Generator Basics

    function* declaration

    Declares generators which return an iterator object instead of running code.

    function* counter() {
      yield 1;
      yield 2;
    }
    const it = counter();
    next() calls

    Resumes execution block internally until encountering the next yield line.

    it.next(); // { value: 1, done: false }
    it.next(); // { value: 2, done: false }
    Return values

    Signals completion with done true and returns standard values if declared.

    function* range() {
      yield 1;
      return "stop";
    }
    const it2 = range();
    it2.next(); // { value: 1, done: false }
    it2.next(); // { value: "stop", done: true }

Controlling Generators

    Value injection

    Passes parameter values back into the generator at pause lines.

    function* greet() {
      const name = yield "name?";
      yield `Hi, ${name}`;
    }
    const g = greet();
    g.next(); // "name?"
    g.next("Ada"); // { value: "Hi, Ada", done: false }
    return() method

    Terminates generator runs early, returning specified values immediately.

    const it = counter();
    it.next();
    it.return("end"); // { value: "end", done: true }
    throw() method

    Injects exceptions directly into generators at the current yield line.

    function* safe() {
      try { yield 1; } catch (e) { yield e.message; }
    }
    const it = safe();
    it.next();
    it.throw(new Error("oops")); // value: "oops"

Lazy Sequences

    Infinite generators

    Computes unending data streams on demand with zero memory leaks.

    function* naturals() {
      let n = 1;
      while (true) yield n++;
    }
    const it = naturals();
    it.next().value; // 1
    take() boundaries

    Extracts limited arrays from lazy sequences using break counters.

    function take(iterable, count) {
      const res = [];
      for (const v of iterable) {
        if (res.length >= count) break;
        res.push(v);
      }
      return res;
    }
    take(naturals(), 3); // [1, 2, 3]

Custom Iterables

    Symbol.iterator method

    Attaches generators to class object structures to allow for-of loops.

    class Range {
      constructor(start, end) {
        this.start = start;
        this.end = end;
      }
      *[Symbol.iterator]() {
        for (let i = this.start; i <= this.end; i++) {
          yield i;
        }
      }
    }
    [...new Range(1, 3)]; // [1, 2, 3]

Delegation with yield*

    Iterable forwarding

    Delegates execution directly to another iterable structure, avoiding loops.

    function* combine() {
      yield* [1, 2];
      yield* "ab";
    }
    [...combine()]; // [1, 2, "a", "b"]

In Practice

FAQ