Iterators And Generators For Beginners

And how to see behind the scenes

·

3 min read

Iterators And Generators For Beginners

Hello World,

Today I want to talk about iterators and generators in Javascript. Don't worry and keep calm. That will be a Back to the basics article.

Let's firstly talk about arrays. As you know they are the most commonly used data structures. They are also a special kind of object whose keys are indexes in string format. If they are a kind of object, that means you can attach properties to them. We can also easily traverse an array via for, for..of ...etc.

const numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];
console.log(Object.keys(numbers)); // -> ["0", "1", "2", "3", "4", "5", "6", "7", "8"]

for(const num of numbers){
    console.info(num);
}

But how can we iterate with for..of over an array. Which mechanism/method provides such an ability? Guess what? That's right it is an iterator mechanism. In the below code, I show what is happening under the hood while we traversing an array via for..of

Whenever we use for..of, it will call the underlying data structure's Symbol.iterator() method. If you log the Array.prototype you will see there is a Symbol(Symbol.iterator) function. Thanks to this implementation we can traverse an array without implementing any extra work.

const iter = numbers[Symbol.iterator]();
iter.next();  // -> {value: 10, done: false}
.....
iter.next();  // -> {value: 0, done: false}
iter.next(); // -> {value:undefined, done:true}

So far so good I guess. What if I want to iterate over an object. As I mentioned before we have to a Symbol.iterator() implementation. This function should return an object which has a next() function. This returned object's next function basically returns a result object which has {value: any, done: boolean} format. With every iteration for..of will call your iterator's next function. That's enough for talk let's see the code.

const obj = {
  data: ["item-1", "item-2", "item-3", "item-4", "item-5"],
  [Symbol.iterator]() {
   //We have to keep this reference. Because it is global 
  //in next function.(Long story with this :) )
    const self = this;
    let index = 0;
    return {
      next() {
       //return { value: self.data[index++], done: index === self.data.length };
        if (index < self.data.length) {
          return { value: self.data[index++], done: false };
        }
        return { done: true };

      },
    };
  },
};

for (const value of obj) {
    console.log(value)
}

Great so far. We can traverse an object anymore. But as you see in the next function there is a lot of boilerplate code. It is cumbersome, error-prone ...etc. Instead of writing all the logic manually, we can use Generators to do this job for us.

Generators are a special kind of function that can be paused and played during the execution.

const obj = {
  data: ["item-1", "item-2", "item-3", "item-4", "item-5"],

     // start(*) indicates that it is a  generator
     *[Symbol.iterator]() {
       for (let i = 0; i < this.data.length; i++) {
         yield this.data[i];
       }
     },

//Same as the above.
 // *[Symbol.iterator]() {
 //   yield* this.data;
 // },
};

for (const value of obj) {
    console.log(value)
}

As simple as that. That is for today. Stay tuned bye-bye.

Did you find this article valuable?

Support Chety's Blog by becoming a sponsor. Any amount is appreciated!