Iterators in ES2015/ES6

  4 mins read  

Iterators in ES2015

Over the last 20 years, JavaScript hasn’t had native support for the iterator pattern. The ES2015 Language Specification changed this by introducing iteration protocols.

The following examples demonstrate how to iterate over iterable objects (e.g.: Array, Map, Set, String, etc…) and define iterable objects using the iteration protocols.

ES5

Developers coming from other programming languages are often surprised by the lack iteration support in JavaScript. Prior to the introduction of iterators, the simplest way to iterate over an array was with a traditional for loop.

const arr = [1, 2, 3];

for (let i = 0; i < arr.length; i++) {
  const number = arr[i]; // extract the number from array
  console.log(number); // prints 1, 2, 3
}

Though it may be common for developers to use a for…in loop to iterate over an object instead of the traditional for loop, the for…in loop only iterates over the enumerable properties and order is not guaranteed.

ES2015

Iterating over an aggregate object has been simplified in ES2015. A for…of loop was introduced to iterate over objects that implement the iterable protocol. In the following example, the for…of loop iterates over an array.

const arr = [1, 2, 3];

for (const i of arr) {
  console.log(i); // prints 1, 2, 3
}

ITERATION PROTOCOLS

The ES2015 language specification introduced an iterable and iterator protocols to support iterating over objects.

ITERABLE PROTOCOL

An iterable must implement the iterable protocol. This protocol requires the object implement an @@iterator method that returns an iterator.

This sounds simple, but instead of defining a method using @@iterator, the method must be defined using the Symbol.iterator with block notation.

class Aggregate {
  [Symbol.iterator]() { // implements the iterable protocol
    // returns an object that implements the iterator protocol
  }
}

ITERATOR PROTOCOL

An iterator must implement the iterator protocol. This protocol requires the object implement a next method. The next method must return an object containing two properties (value and done).

value: can contain any object
done: a boolean flag identifying the completion of the iteration

CUSTOM ITERATOR

In the following example, the Aggregate’s @@iterator method is updated to return an iterator. The iterator will produce a sequence of values.

class Aggregate {
  constructor() {
    this.values = [1, 2, 3]; // internal collection
  }

  [Symbol.iterator]() { // implements the iterable protocol
    let index = 0;
    return {  // returns an iterator protocol
      next: () => {
        const value = this.values[index];
        const done = index++ >= this.values.length;
        return { value, done };
      }
    };
  }
}

In the following example, the values returned from the the Aggregate’s iterator are logged to the console. The first, second, and third objects returned contain the values from the Aggregate’s internal array and the done flag set to false. The last object returned has the done flag set to true, signaling the iteration is over.

const agg = new Aggregate();

const iter = agg[Symbol.iterator]();

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

Now that the Aggregate class is iterable, the for…of loop can be used to iterate the object without exposing its inner collection.

const agg = new Aggregate();

for (const i of agg) {
  console.log(i); // prints 1, 2, 3
}

SUPPORT

As of this post, nodejs provides support for the iterable and iterator protocols, but only a few browsers support the protocols. However, this doesn’t mean that developers cannot use these features on the client-side now. Polyfills and trans-compilers like Babel can enable these features now.