Close

JavaScript - Creating Custom Iterable by applying ES6 Iterable and Iterator Protocols

[Updated: Nov 23, 2018, Created: Nov 13, 2018]

To create custom iterables (so that we can use it in for-of loops), we need to implement iterable protocol and iterator protocol. A protocol means a set of rules which we need to follow.

The iterable Protocol

According to this protocol:

In order to be iterable, an object must implement the iterator method.

This can be done by creating a member function named as Symbol.iterator (a built-in Symbol, check out tutorial). This function must be a zero arguments function that returns an object, conforming to the iterator protocol (next section).

An iterable looks like this:

let obj = {
 // applying iterable protocol by defining iterator method
 [Symbol.iterator](){
   .........
 }
}

In [Symbol.iterator], the square bracket encloses computed function name (tutorial);

The iterator Protocol

According to this protocol:

An iterator method implements next() method which returns an object having two properties:
First property's name is done of type boolean (its value should be 'false' if there are still items to be iterated, otherwise it should be 'true').
Second property's name is value which returns the loop's current value until done=true.

next() method implementation looks like this

let next = function(){
 return {
  done : true/false, //decide true/false dynamically,
  value: val //evaluate value for current iteration until done=true
 };
}

An object enclosing this function, must be returned from iterable's iterator method (iterable protocol)

Combining the two protocols

let next = function(){
    return {
        done : true/false, //decide true/false dynamically,
        value: someValue //evaluate value for current iteration until done=true
    };
};
let myObj = {
    [Symbol.iterator](){//the iterator method
        return { next };
    }
};

Example:

In following example we will create a class which will be iterable and whose elements will be even numbers for the provided range of integers:

class EvenNumbers {
    constructor(start, end) {
        this._start = 2 * Math.round(start / 2) - 2;
        this._end = end;
    }

    //implementing iterable protocol
    [Symbol.iterator]() {
        let [current, end] = [this._start, this._end];
        return { //returns the iterator object 
            next() { //implementing next() method
                if (current <= end) {
                    current += 2;
                }
                if (current > end) {
                    return { done: true }
                } else {
                    return { value: current, done: false }
                }

            }
        }
    }
}

let evenNums = new EvenNumbers(5, 11);
//using for-of loop
for (let n of evenNums) {
    console.log(n);
}

console.log("-----------");
console.log("using next() method:");
console.log("-----------");
let evenIterator = evenNums[Symbol.iterator]();
while (true) {
    let obj = evenIterator.next();
    console.log(obj);
    if (obj.done) {
        break;
    }
}

In above example we used shorthand method syntax (tutorial) for next() method.

In the last tutorial we went through a lot of for-of examples, there we can apply next() methods as well. For example:

let fruits = ["apple", "banana", "orange"];
let fruitsItr = fruits[Symbol.iterator]();
console.log(fruitsItr.next());
console.log(fruitsItr.next());
console.log(fruitsItr.next());
console.log(fruitsItr.next());
console.log(fruitsItr.next());

Both iterable or iterator can be used in for-of loop

let nums = [1, 3, 5]; //array is an iterable
for (let n of nums) {
    console.log(n);
}
console.log("------");
//getting nums iterator
let numsItr = nums[Symbol.iterator]();
//for-of can also used for numsItr
for (let n of numsItr) {
    console.log(n);
}

See Also