Javascript - Iterables and Iterators

Introduction

An Iterable is a series of elements that can be iterated over.

An Iterable has one method that produces an Iterator.

An Iterator is the object with an iteration state.

All iterator objects come with a next() method to return the next object.

The object returned from the next() method has two properties:

  • value, which is the next value in the collection; and
  • done, which is a Boolean that signals whether the sequence has ended, it is false as long as there are values in the sequence to return and only returns true when the collection has run out of values.

Each call to the next() method produces the next value in the collection.

Once all the values of the collection are iterated, calling the next() method will return the value as undefined and done as true.

The following code show show to manually implement an iterator in ES6:

function myIterator(data) { 
   let currentIndex = 0; 
   return { 
       next: () => { 
          const done = (currentIndex >= data.length); 
          const value = !done ? data[currentIndex] : undefined; 
          currentIndex += 1; 
          return { done, value  }; 
       } 
   }; 
} 

const itrObj = myIterator([1, 2, 3]); 
itrObj.next();  // { value: 1, done: false } 
itrObj.next();  // { value: 2, done: false } 

itrObj.next();  // { value: 3, done: false } 
itrObj.next();  // { value: undefined, done: true } 

// for all further calls 
console.log(itrObj.next());    // { value: undefined, done: true } 

Here, the myIterator() function returns an object that has a next() method.

When called each time returns the next value of the collection.

After the last element, the done value of the object returned becomes true and the value returned is always undefined.

ES6 simplifies the process of implementing iterators via [Symbol.iterator].

It specifies the default iterator for an object.

Let's look at the same example as above in ES6 using Symbol.iterator:

const arr = [1, 2, 3]; 
const itrObj = arr[Symbol.iterator](); 
itrObj.next(); // { value: 1, done: false } 
itrObj.next(); // { value: 2, done: false } 
itrObj.next(); // { value: 3, done: false } 
itrObj.next(); // { value: undefined, done: true } 

To create iterator out of string,

const message = "This is a test"; 
const itrObj = message[Symbol.iterator](); 

itrObj.next();      // { value: "T", done: false } 
itrObj.next();      // { value: "h", done: false } 

Here, we iterate over each character of the String message using the next() method.

ES6 allows us to iterate over Map and Set collections.

const permissionMap = new Map(); 

permissionMap.set("admin", {read: true, write: true}); 
permissionMap.set("student", {read: true, write: false}); 
permissionMap.set("faculty", {read: true, write: true}); 
permissionMap.set("staff", {read: true, write: false}); 

const permissions= permissionMap[Symbol.iterator](); 
console.log(permissions.next()); 
// {value: ['admin', { read: true, write: true}], done: false} 
console.log(permissions.next()); 
// {value: ['student', { read: true, write: false}], done: false} 
console.log(permissions.next()); 
// {value: ['faculty', { read: true, write: true}], done: false} 
console.log(permissions.next()); 
// {value: ['staff', { read: true, write: false}], done: false} 
console.log(permissions.next()); // {value: undefined, done: true} 

Here, we have created a map for the permissions for different user types as the key.

We can create a new iterator and iterate over the values using next().

These collections also provide API method(s) to generate an iterator.

For example, you can get the iterator of a map using the entries() method:

const permissionEntries = permissionMap.entries(); 
console.log(permissionEntries.next()); 
// {value: ['admin', { read: true, write: true, del: true }], done: false} 
console.log(permissionEntries.next()); 
// {value: ['student', { read: true, write: false, del: false }], done: false}