JavaScript Generators

Generators are special functions that can pause and resume their execution. They are declared with function* syntax and use the yield keyword to produce values on demand. Generators are both iterators and iterables.

Generator Basics: function* and yield

A generator function is defined with function* (or function *). When called, it returns a generator object without executing the body. Each call to next() executes until the next yield statement.

Basic Generator
function* greetings() {
  yield "Hello";
  yield "World";
  yield "!";
}

const gen = greetings();

// Each next() runs until the next yield
console.log(gen.next()); // { value: 'Hello', done: false }
console.log(gen.next()); // { value: 'World', done: false }
console.log(gen.next()); // { value: '!', done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Generators are iterable
for (const word of greetings()) {
  console.log("for...of:", word);
}

// Spread into array
console.log("Spread:", [...greetings()]);

yield and next() Communication

Generators support two-way communication. You can send values INTO a generator by passing an argument to next(). The sent value becomes the result of the yield expression.

Sending Values to Generators
function* calculator() {
  const a = yield "Enter first number:";
  const b = yield "Enter second number:";
  yield `Sum: ${a + b}`;
}

const calc = calculator();
console.log(calc.next());       // { value: 'Enter first number:', done: false }
console.log(calc.next(10));     // { value: 'Enter second number:', done: false }
console.log(calc.next(20));     // { value: 'Sum: 30', done: false }
console.log(calc.next());       // { value: undefined, done: true }

// Accumulator generator
function* accumulator() {
  let total = 0;
  while (true) {
    const value = yield total;
    total += value;
  }
}

const acc = accumulator();
console.log(acc.next());      // { value: 0, done: false }
console.log(acc.next(5));     // { value: 5, done: false }
console.log(acc.next(10));    // { value: 15, done: false }
console.log(acc.next(3));     // { value: 18, done: false }
📝 Note: The first call to next() always ignores any argument you pass, because there is no yield expression waiting to receive a value yet. Values are sent starting from the second next() call.

return() and throw()

Generators have return() to end the generator early and throw() to throw an error inside the generator at the point of the current yield.

return() and throw()
function* counter() {
  let i = 0;
  while (true) {
    try {
      yield i++;
    } catch (e) {
      console.log("Caught:", e.message);
    }
  }
}

// return() - ends the generator
const gen1 = counter();
console.log(gen1.next());
console.log(gen1.next());
console.log(gen1.return(99));  // { value: 99, done: true }
console.log(gen1.next());      // { value: undefined, done: true }

// throw() - throws error at yield
const gen2 = counter();
console.log(gen2.next());
console.log(gen2.throw(new Error("Oops")));
console.log(gen2.next());

Generators as Iterators

Since generators implement both the iterator and iterable protocols, they are the easiest way to create custom iterables.

Generator as Iterator
// Range generator
function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) {
    yield i;
  }
}

console.log("Range 1-5:", [...range(1, 5)]);
console.log("Range 0-10 by 2:", [...range(0, 10, 2)]);

// Make an object iterable with a generator
const team = {
  members: ["Alice", "Bob", "Charlie"],
  *[Symbol.iterator]() {
    for (const member of this.members) {
      yield member;
    }
  }
};

for (const member of team) {
  console.log("Member:", member);
}
console.log("Spread team:", [...team]);

Lazy Evaluation and Infinite Sequences

Generators compute values lazily -- they only produce the next value when asked. This makes them perfect for infinite sequences and processing large datasets without loading everything into memory.

Lazy Evaluation
// Infinite Fibonacci
function* fibonacci() {
  let a = 0, b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

// Take first N values from any generator
function take(gen, n) {
  const result = [];
  for (const val of gen) {
    result.push(val);
    if (result.length >= n) break;
  }
  return result;
}

console.log("First 10 Fib:", take(fibonacci(), 10));

// Infinite IDs
function* idGenerator(prefix = "id") {
  let n = 1;
  while (true) {
    yield `${prefix}_${n++}`;
  }
}

const ids = idGenerator("user");
console.log(ids.next().value);
console.log(ids.next().value);
console.log(ids.next().value);

// Compose generators with yield*
function* concat(...generators) {
  for (const gen of generators) {
    yield* gen;
  }
}

console.log([...concat(range(1, 3), range(10, 12))]);
FeatureDescription
function*Declares a generator function
yieldPauses and emits a value
yield*Delegates to another iterable/generator
next()Resumes execution, optionally sends value
return()Ends generator, returns given value
throw()Throws error at current yield point
Exercise:
What does yield* do in a generator?
Try it YourselfCtrl+Enter to run
Click Run to see the output here.