Making JS Objects iterable
Tags: javascript, generators, iterable, objects
Published: 2021-05-30 09:27:38

*Disclaimer*: This is a fun task that I tried doing. I don't see a real world use case for this, especially because now that we have Maps in JS. Let me know in the comments if you can think of something.

Now thats out of the way, let's get to it.

As we know, Objects in JS are not iterable. That means you cannot use them with for...of. You must've come across errors similar to:

`TypeError: 'x' is not iterable`

###What are we trying to achieve?
We're trying to understand the technicalities behind the above error. And we will do it by making an object iterable.

###What does it mean when we say `iterable`?
When a value is iterable, under the hood, that value has an implementation of the iterable protocol.

That means, the prototype of that element must have a method which goes like:

`[Symbol.iterator](){}`

..and this method is supposed to return an object like:

```js
{
      next(){
        //we'll get to the definition of this method
      }
}
```

..and this next() method will be called by the iterating functions like for...of. Each time they call next(), they expect an object of the syntax:

`{ value: <value of current iteration>, done: <boolean> }`

The `value` will be made available to the value in `for(const value of element)`, and `done` will be used to know if the iteration need to be stopped or continue.

###What will we do?
We'll take the object `const range = {from:1, to: 5}` and try to make a for...of print the values between. That is, the output should be: `1, 2, 3, 4, 5`.

Let's write the code and explain what is being done.

```js
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator](){
    return {
      next: () => {
        if(this.from <= this.to){
          return { value: this.from++, done: false };
        }else{
          return { done: true };
        }
      }
    }
  }
}
```

Here we've added a new property (method) to our object, with the key `Symbol.iterator`. The for..of function will look for the implementation of this key, and it doesnt have it, it will throw the error we mentioned at the beginning of the blog. And as per the spec, Symbol based keys need to be created with square brackets around.

This new method returns an object (like we mentioned a bit above), which has next method in it. The logic of the next method is self explanatory. It increments the value of **from** till it reaches **to**, and on each iteration it returns an object with value and done keys in it.

When the done = false (in the last iteration), the for...of method will stop iterating it further.

###Problem with the above code
If you notice, the next method is modifying the value of original property **from**. At the end of the iteration, it would have reached 6, which is not good. Because we dont want `range = {from: 1, to: 5}` to become `range = {from: 6, to: 5}`. So what do we do?

```js
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator](){
    return {
      start: this.from,
      end: this.to,

      next(){
        if(this.start <= this.end){
          return { value: this.start++, done: false };
        }else{
          return { done: true };
        }
      }
    }
  }
}
```

We've added **start** and **end** variables under the local scope of the object we're returning. We could have kept the same name as **from**, **to**, but that would have created confusion while reading. 

Also we've replaced the arrow function with a regular function so that the `this` inside the next() points to the object that we return. Otherwise next() wont have access to **start** and **end** properties.

###Lets use Generators to further optimise this code

(Generator functions)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*] were designed to solve these kind of use cases. When called, they return an object with **next** method in it. And that method returns something like this:

`{ value: <value of current iteration>, done: <boolean> }`

..which is exactly what our for..of needs. 

Lets try modifying our code to use generator function.

```js
let range = {
  from: 1,
  to: 5,

  *[Symbol.iterator](){
    for(let value=this.from; value<=this.to; value++){
      yield value;
    }
  }
}
```

Every time iterator method gets called, the loop runs and the yield returns the value of the index(1) and pauses the execution, waiting for next call. Next time for..of calls, it resumes execution from where it paused and returns next index value(2). So and so forth till it exits the loop.

Voila! That was simple and clean. Hope you understood how iterator protocol, and generators work.