Using Generator Functions as Iterators

If you're getting a little more advanced in your JavaScript, you may have started using the [Symbol.iterator] to make your objects iterable. For instance, I have implemented a Binary Search Tree class and wanted to be able to iterate through the collection using JavaScript's for...of and also wanted to be able to use the ... spread operator with it. That is exactly what [Symbol.iterator] is for.

For an object to implement the iterator protocol it needs to implement a next() method with the following semantics:

A zero arguments function that returns an object with at least the following two properties:

  • done (boolean)
    • Has the value true if the iterator is past the end of the iterated sequence. In this case value optionally specifies the return value of the iterator.
    • Has the value false if the iterator was able to produce the next value in the sequence. This is equivalent of not specifying the done property altogether.
  • value - any JavaScript value returned by the iterator. Can be omitted when done is true.

The next method always has to return an object with appropriate properties including done and value. If a non-object value gets returned (such as false or undefined), a TypeError ("iterator.next() returned a non-object value") will be thrown.

Like me, you may have learned about [Symbol.iterator] and the iterable and iterator protocol before learning about generators. So you might have been implementing your [Symbol.iterator]s to return exactly that kind of object. For example, let's say you were making your own Array2D class and wanted it to implement the iterable protocol, you might have written something like this:

class Array2D {
  constructor(array) {
    this.array = array;
  }

  [Symbol.iterator]() {
    let i = 0;
    let j = 0;
    return {
      next: () => {
        while (i < this.array.length) {
          if (j < this.array[i].length) {
            return {
              value: this.array[i][j++],
              done: false
            };
          }
          i++;
          j = 0;
        }
        return { done: true, value: undefined };
      }
    };
  }
}

This works fine, but is kind of an eyesore. Generators can help with that. Here is what the same class could look like with a generator function:

class Array2D {
  constructor(array) {
    this.array = array;
  }

  *[Symbol.iterator]() {
    for (let i = 0; i < this.array.length; i++) {
      for (let j = 0; j < this.array[i].length; j++) {
        yield this.array[i][j];
      }
    }
  }
}

As you can see, using a generator allows for much more idiomatic looking code and saves you from having to return an object that conforms to the iterable protocol.

Have you used iterators or generators before? What do you like about them? Let me know in the comments below.