What are JavaScript Generator Functions?

Generator functions in JavaScript are special functions that can be paused and resumed, allowing them to yield multiple values over time. Unlike regular functions that return a single value, generators can produce a sequence of values, making them extremely useful for handling iterative algorithms, managing asynchronous operations, and working with large data sets.

Key Point: Generator functions are defined using the function* syntax and use the yield keyword to pause execution and return values.

Generator Function Syntax

The syntax for creating generator functions involves using the function* declaration and the yield operator:

function* myGenerator() {
  yield 'First value';
  yield 'Second value';
  return 'Final value';
}

All of these syntax variations are valid:

Syntax Example Validity
function* generator() function* myGenerator() { } ✅ Valid (Most Common)
function *generator() function *myGenerator() { } ✅ Valid
function*generator() function*myGenerator() { } ✅ Valid

How Generator Functions Work

When you call a generator function, it doesn’t execute its code immediately. Instead, it returns a special object called a Generator object. This object implements the iterator protocol, meaning you can call next() on it to execute code until the next yield expression.

Basic Generator Example

function* simpleGenerator() {
  yield 'Hello';
  yield 'World';
  yield '!';
}

const gen = simpleGenerator();

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 }

Generator Object Methods

The generator object returned by a generator function has three main methods:

Method Description Example
next() Resumes execution until the next yield expression gen.next()
return() Returns the given value and terminates the generator gen.return('value')
throw() Throws an error into the generator at the point where it was paused gen.throw(new Error())

Using yield* for Delegation

The yield* expression is used to delegate to another generator or iterable object. This is useful for calling one generator from another or implementing recursive generator functions.

Generator Delegation Example

function* generatorOne() {
  yield 'One';
  yield 'Two';
}

function* generatorTwo() {
  yield 'Start';
  yield* generatorOne(); // Delegating to generatorOne
  yield 'End';
}

const gen = generatorTwo();

for (const value of gen) {
  console.log(value);
}
// Output: 'Start', 'One', 'Two', 'End'

Generators and Iterators

Since generators are iterable, they work seamlessly with loops and the spread operator. This makes them perfect for creating custom iterable objects.

Custom Iterable with Generator

const customIterable = {
  start: 1,
  end: 5,
  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
};

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

Practical Use Cases for Generators

Generators are particularly useful for:

  • Implementing custom iterators for complex data structures
  • Handling asynchronous operations in a synchronous-looking way
  • Generating infinite sequences (like Fibonacci series)
  • Managing state machines
  • Lazy evaluation of data

Infinite Sequence Example

function* infiniteSequence() {
  let num = 0;
  while (true) {
    yield num++;
  }
}

const numbers = infiniteSequence();

console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
// This can continue indefinitely