Bitwise Breakdown

Generator Function in Javascript

Published on 4 min read

What is a Generator Function?

A generator function allows you to build custom iterators with ease. Generator functions are defined using the function* syntax.

When called, a generator function doesn’t execute its body immediately. Instead, it returns a special iterator, known as the generator object. The generator function is executed when you call the object’s next() method and this execution stops when first yield keyword is encountered, and returns an object containing two properties:

  • value: the yielded value
  • done: a boolean indicating if the generator has finished execution When object’s next() method is called again, it resume its execution from where it stopped. You can create multiple objects of a generator function, each maintaining its own state. Let’s dive into an example.
javascript
function* generator() {
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
 
const generatorObject = generator();
 
console.log(generatorObject.next()); // { value: 1, done: false }
console.log(generatorObject.next()); // { value: 2, done: false }
console.log(generatorObject.next()); // { value: 3, done: false }
console.log(generatorObject.next()); // { value: 4, done: true }
console.log(generatorObject.next()); // { value: undefined, done: true }

Iterating Over a Generator Object

You can iterate over a generator object in two ways:

  • Using the spread (...) operator
  • Using a for...of loop Here’s an example:
javascript
function* generator() {
  yield 1;
  yield 2;
  yield 3;
}
 
// Using the spread operator
let arr = [...generator()];
 
// Using a for...of loop
for (let value of generator()) {
  console.log(value); // Logs 1, 2, 3
}

Knowledge Check: What’s the Output?

Let’s test your knowledge with this question:

Challenge 1

Guess the output of the below code.

javascript
function* generator() {
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
 
let arr = [...generator()];
console.log(arr);

If you guessed [1, 2, 3], you’re correct!

The reason behind this behavior is that the return statement in a generator is treated differently. The spread operator and for…of loop collect only the yielded values (1, 2, and 3) and stop once done: true is encountered. The return value (4) is ignored in the iteration process as return keyword returns object with done: true.

Challenge 2

What do you think will happen here?

javascript
function* generator() {
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
 
let g = generator();
let arr1 = [...g];
let arr2 = [...g];
console.log(arr1);
console.log(arr2);

The output will be

console
[1, 2, 3]
[]

This is because, when an iterator has been consumed (like in the first arr1), it cannot be reused. You can, however, create a new generator object to start the iteration again.

Where it can be used?

Generator functions not just helps in building an iterator, but also provides a unique way to handle tasks that require pausing and resuming.

Now that you have a solid understanding of generator functions, let’s look at a few real-world scenarios where they can be used.

Finite State Machines

A state machine cycles through different states based on specific rules. Generators make it easy to switch between these states in a controlled manner without needing complex conditionals or manually tracking the current state. I have provided simple traffic signal as an example, but it can be used in many cases.

javascript
function* trafficSignal() {
  while (true) {
    yield 'red';
    yield 'yellow';
    yield 'green';
  }
}
 
const signal = trafficSignal();
console.log(signal.next().value); // 'red'
console.log(signal.next().value); // 'yellow'
console.log(signal.next().value); // 'green'

Iterator for data structures

If you are building custom data structure to handle business use cases, you can provide easy way to access the values by providing a iterator. In this example, I have used tree and iterating it as inorder traversal.

javascript
function* traverseTree(node) {
  if (node) {
    yield node.value;
    if (node.left) {
      yield* traverseTree(node.left);
    }
    if (node.right) {
      yield* traverseTree(node.right);
    }
  }
}
 
// Example tree structure
const tree = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4 },
    right: { value: 5 }
  },
  right: {
    value: 3,
    left: { value: 6 },
    right: { value: 7 }
  }
};
 
const treeIterator = traverseTree(tree);
 
for (let value of treeIterator) {
  console.log(value); // Will log all tree values in order
}

Conclusion

Generator functions are a truly versatile feature in JavaScript, letting you tackle a wide range of tasks with ease. Once you get comfortable with generators, you’ll find they can be a game-changer for handling iterative tasks in your projects.