Generators

Generators are functions that can pause and resume execution. They provide an easier way to create iterators.

Generator Syntax

Use function* to define a generator and yield to pause:

function* simpleGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = simpleGenerator();

console.log(gen.next());  // { value: 1, done: false }
console.log(gen.next());  // { value: 2, done: false }
console.log(gen.next());  // { value: 3, done: false }
console.log(gen.next());  // { value: undefined, done: true }

// Generators are iterable
for (const value of simpleGenerator()) {
  console.log(value);  // 1, 2, 3
}

Generator with Logic

function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) {
    yield i;
  }
}

console.log([...range(1, 10, 2)]);  // [1, 3, 5, 7, 9]

for (const num of range(5, 8)) {
  console.log(num);  // 5, 6, 7, 8
}

Infinite Generators

A generator can produce an infinite sequence. Each value is evaluated lazily, which means it is computed only when we ask for the next one:

function* infiniteCounter() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

const counter = infiniteCounter();
console.log(counter.next().value);  // 0
console.log(counter.next().value);  // 1
console.log(counter.next().value);  // 2
// ... continues forever

// Take first 5
function* take(iterable, n) {
  let count = 0;
  for (const item of iterable) {
    if (count++ >= n) return;
    yield item;
  }
}

console.log([...take(infiniteCounter(), 5)]);  // [0, 1, 2, 3, 4]

Fibonacci Generator

function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacci();
for (let i = 0; i < 10; i++) {
  console.log(fib.next().value);
}
// 1, 1, 2, 3, 5, 8, 13, 21, 34, 55

Generator Methods

Delegating with yield*

Delegate to another generator or iterable:

function* gen1() {
  yield 1;
  yield 2;
}

function* gen2() {
  yield* gen1();  // Delegate to gen1
  yield 3;
  yield* [4, 5];  // Delegate to array
}

console.log([...gen2()]);  // [1, 2, 3, 4, 5]

Passing Values to Generators

The next() method can pass values back into the generator:

function* conversation() {
  const name = yield "What is your name?";
  const age = yield `Hello ${name}! How old are you?`;
  yield `${name} is ${age} years old.`;
}

const chat = conversation();
console.log(chat.next().value);  // "What is your name?"
console.log(chat.next("Alice").value);  // "Hello Alice! How old are you?"
console.log(chat.next(25).value);  // "Alice is 25 years old."

Generator Return and Throw

function* gen() {
  try {
    yield 1;
    yield 2;
    yield 3;
  } finally {
    console.log("Cleanup");
  }
}

const g = gen();
console.log(g.next());  // { value: 1, done: false }
console.log(g.return(99));  // Cleanup, { value: 99, done: true }

// Or throw an error into the generator
const g2 = gen();
g2.next();
// g2.throw(new Error('Something went wrong'));