Iterators - Modern Javascript

Iterators - Modern Javascript

Part 1

ES6 has introduced new ways of working with functions and objects in Javascript. In this article, we are going to discuss Iterators and Generators.

What are Iterables??

Before discussing Iterators, we need an understanding of Iterables in Javascript. As the name suggests, Iterables are the objects that allow us to iterate over them. Most likely you must have used Iterables before like Array, String, etc. In terms of data structure, Objects that have the Symbol.iterator() method are Iterables. We will discuss more about Symbol.iterator() in next section .

A `for...of can be used to iterate a collection. The for...of loop requires an iterable. Otherwise, it will throw TypeError.

For example,

let collectn = [1, 2, 3];
for(let n of collectn){
    console.log(n);  
}

Following objects are already Iterables in Javascript

  • Arrays
  • Strings - can iterate over each character
  • Maps - can iterate using Key-value pair
  • Sets - can iterate over values

Iterators

Iterators are a new way to iterate a sequence of values or any collection. They are objects which allow us to access one value at a time from collections. In terms of structure, Iterators are the object which implements below interface

interface Iterator<T> {
    next(value?: any): IteratorResult<T>;
    return?(value?: any): IteratorResult<T>;
    throw?(e?: any): IteratorResult<T>;
}

next() allows us to access the next element of the iterable (one at a time). IteratorResult is the combination of 'value' and 'done' properties.

interface IteratorResult<T> {
    value: T;
    done: boolean;    
}
  • value property can be of any data type and returns the current value in the sequence.
  • done property is a boolean value that indicates whether the iteration is complete or not. It returns true once the iteration is complete, else false.

Let's check the above code block with Iterators

let collectn: number[] = [1, 2, 3];
let nIterator: any = collectn[Symbol.iterator]();
console.log(nIterator.next()); // value -> 1, done -> false
console.log(nIterator.next()); // value -> 2, done -> false
console.log(nIterator.next()); // value -> 3, done -> false
console.log(nIterator.next()); // value -> undefined, done -> true

Usage

Some of you must be thinking it is easy to use methods like for, foreach, for..of, while, etc to iterate over objects. Consider a complex object myBooks having nested objects

const myBooks = {
  English: {
    'Dr.Mark',
    'J. K. Rowling'
  },
  Science: {
    'Dr.Neal',
    'Rober Aslom'
  },
  Fiction: {
    'Terry Machinst',
    'J. K. Rowling'
  },
}

Can you think about how we can iterate over this object? If we try to use the for...of loop here, we will get the following error:

for(let auth of myBooks){
//Remember `for...of` loop requires an `Iterable` to iterate. 
  console.log(auth);              //TypeError: { } is not iterable
}

In order to fetch all the writers, we need to use multiple loops. There is no direct way to get all the writers from our custom object myBooks. we need a method in our object that can return our data sequentially. In order to iterate, we need to make our custom object Iterable

Let's add a method in our object that returns all writers.

const myBooks = {
  writers: {
    English: {
      'Dr.Mark',
      'J. K. Rowling'
    },
    Science: {
      'Dr.Neal',
      'Rober Aslom'
    },
    Fiction: {
      'Terry Machinst',
      'J. K. Rowling'
    },
  },
  getWriters(){
    const writers = [];

    for(const writer of this.writers.English){
      console.log(writer);
    }
    for(const writer of this.writers.Science){
      console.log(writer);
    }
    for(const writer of this.writers.Fiction){
      console.log(writer);
    }
  }
}

The above code will solve our problem and returns writers' data sequentially. But this code block still has some limitations. For this implementation, the developer has to know the exact data structure of our custom objects like property name and return type of the method that returns all the data. Different developers will deal with it differently. Here Iterators come into play. Let's solve the above problem using Iterators.

const myBooks = {
  English: {
    'Dr.Mark',
    'J. K. Rowling'
  },
  Science: {
    'Dr.Neal',
    'Rober Aslom'
  },
  Fiction: {
    'Terry Machinst',
    'J. K. Rowling'
  },
},
  //Remember: Iterators are the objects which implement iterator interface
  [Symbol.iterator]() { 

    const subjects= Object.values(this.myBooks);

    let currentWriterIndex = 0;
    let currentSubjectIndex = 0;

    return {
      //Iterators have the next() which will return an object with keys value and done.
      next() {  
        const writers = subjects[currentSubjectIndex];

        const isLastWriter = !(currentWriterIndex < writers.length);
        if (isLastWriter) {
          currentSubjectIndex++;
          currentWriterIndex = 0;
        }

        const isLastSubject= !(currentSubjectIndex < subjects.length);
        if (isLastSubject) {
          return {
            value: undefined,
            done: true            <- done indicates whether iteration is complete or not
          };
        }

        return {
          value: subjects[currentSubjectIndex][currentWriterIndex++],
          done: false
        }
      }
    };
  }
}

We have created an Iterator in our myBooks custom object using System.iterator. As mentioned before, Iterator has the next() method which will return an object with keys value and done. value property will contain the current value, and done property indicates whether iteration is complete or not.

Now with iterator implemented in our custom object, we can now use our custom object simply using for..of or any other iteration method.

for(let auth of myBooks)
{
    console.log(auth);
}

Custom Iterators are a useful tool. Their implementation requires careful programming as it can also lead to an infinite loop.

In our next article, we are going to discuss Generators. They provide a powerful alternative to Iterators by defining a single function whose execution is not continuous.

I hope this article will help you to understand the Iterables and Iterators. If you have any suggestions, just let me know in the comments section.