The Essential Guide to JavaScript Iterators

Created with Sketch.

The Essential Guide to JavaScript Iterators

Summary: in this tutorial, you will learn about JavaScript iterator and how to use iterators to process a sequence of data more efficiently.

The for loop issues

When you have an array of data, you typically use a for loop to iterate over its elements. For example:

let ranks = ['A', 'B', 'C'];

for (let i = 0; i < ranks.length; i++) {
console.log(ranks[i]);
}

Code language: JavaScript (javascript)

The for loop uses the variable  i to track the index of the ranks array. The value of  i increments each time the loop executes as long as the value of i is less than the number of elements in the ranks array.

This code is straightforward. However, its complexity grows when you nest a loop inside another loop. In addition, keeping track of multiple variables inside the loops is error-prone.

ES6 introduced a new loop construct called for...of to eliminate the standard loop’s complexity and avoid the errors caused by keeping track of loop indexes.

To iterate over the elements of the ranks array, you use the following for...of construct:

for(let rank of ranks) {
console.log(rank);
}

Code language: JavaScript (javascript)

The for...of is far more elegant than the for loop because it shows the true intent of the code – iterate over an array to access each element in the sequence.

On top of this, the for...of loop has the ability to create a loop over any iterable object, not just an array.

To understand the iterable object, you need to understand the iteration protocols first.

Iteration protocols

There are two iteration protocols: iterable protocol and iterator protocol.

Iterator protocol

An object is an iterator when it implements an interface (or API) that answers two questions:

  • Is there any element left?
  • If there is, what is the element?

Technically speaking, an object is qualified as an iterator when it has a next() method that returns an object with two properties:

  •  done: a boolean value indicating whether or not there are any more elements that could be iterated upon.
  •  value: the current element.

Each time you call the next(), it returns the next value in the collection:

{ value: 'next value', done: false }

Code language: CSS (css)

If you call the next() method after the last value has been returned, the next() returns the result object as follows:

{done: true: value: undefined}

Code language: CSS (css)

The value of the done property indicates that there is no more value to return and the value of the property is set to undefined.

Iterable protocol

An object is iterable when it contains a method called [Symbol.iterator] that takes no argument and returns an object which conforms to the iterator protocol.

The [Symbol.iterator] is one of the built-in well-known symbols in ES6.

Iterators

Since ES6 provides built-in iterators for the collection types  Array, Set, and Map, you don’t have to create iterators for these objects.

If you have a custom type and want to make it iterable so that you can use the for...of loop construct, you need to implement the iteration protocols.

The following code creates a Sequence object that returns a list of numbers in the range of ( start, end) with an interval between subsequent numbers.

class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
};

Code language: JavaScript (javascript)

The following code uses the Sequence iterator in a for...of loop:

let evenNumbers = new Sequence(2, 10, 2);

for (const num of evenNumbers) {
console.log(num);
}

Code language: JavaScript (javascript)

Output:

2
4
6
8
10

 

You can explicitly access the [Symbol.iterator]() method as shown in the following script:

let evenNumbers = new Sequence(2, 10, 2);
let iterator = evenNumbers[Symbol.iterator]();

let result = iterator.next();

while( !result.done ) {
console.log(result.value);
result = iterator.next();
}

Code language: JavaScript (javascript)

Cleaning up

In addition to the next() method, the [Symbol.iterator]() may optionally return a method called return().

The return() method is invoked automatically when the iteration is stopped prematurely. It is where you can place the code to clean up the resources.

The following example implements the return() method for the Sequence object:

class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
},
return: () => {
console.log('cleaning up...');
return { value: undefined, done: true };
}
}
}
}

Code language: JavaScript (javascript)

The following snippet uses the Sequence object to generate a sequence of odd numbers from 1 to 10. However, it prematurely stops the iteration. As a result, the return() method is automatically invoked.

let oddNumbers = new Sequence(1, 10, 2);

for (const num of oddNumbers) {
if( num > 7 ) {
break;
}
console.log(num);
}

Code language: JavaScript (javascript)

Output:

1
3
5
7
cleaning up...

 

In this tutorial, you have learned about the JavaScript iterator and how to use the iteration protocols to implement customized iteration logic.

Leave a Reply

Your email address will not be published. Required fields are marked *