DEV Community

Varun Dey
Varun Dey

Posted on • Edited on

Iterators, Generators and Iteration Protocol in Javascript

This post was originally published on my website

If you have been writing JavaScript for a while, you might be well aware what data types are iteratables in JavaScript. If you are not or just can't remember from top of your head, it's String, Array, Map, Set and TypedArray.

But Varun, except String isn't everything here an implementation of Object?

Iterable protocol

You would be absolutely correct to think that. After all most data-types in JavaScript are derived from Object. So what makes Array, Map, Set and TypedArray an iteratable but not Object? Let's open up our console and find out.

Array.prototype[Symbol.iterator]
Map.prototype[Symbol.iterator]
Set.prototype[Symbol.iterator]
Int16Array.prototype[Symbol.iterator]
Object.prototype[Symbol.iterator]

You might have noticed that except the last statement, every line returns us a function. All the remaining object type have a property called Symbol.iterator up their prototype chain. Since this property is not available in Object it returns undefined. Thus, for an object to be iteratable, it must implement iterable protocol which means that the given object must have a Symbol.iterator up it's prototype chain. Symbol.iterator is a function which takes no argument and returns an Object. This returned Object should follow convention of iterator protocol.

Iterator Protocol

Iterator protocol states that for an iterator object, there is a standard way in which the values should be returned back. The object returned from Symbol.prototype is said to be adhering to iterator protocol if it has a method next which returns following two properties:

  • done [boolean] A boolean value denoting if the iteration sequence has finished
  • value Any value returned while iterating. Can be optional when done is true

Let's prove what we've learnt so far

const map = new Map()
mapIterator = map[Symbol.iterator]()
mapIterator.next          // function next()

This means that Map implements

  • Iterable protocol
    • becuase it has Symbol.iterator in it's __proto__ chain.
  • Iterator protocol
    • because iterable protocol returns an Object which has a method next in it.

Iteration protocol in action

Let's put our theory to test on some actual data types

const string = "Hello"
const stringIterator = string[Symbol.iterator]()
stringIterator.next()       // Object { value: "H", done: false }
stringIterator.next()       // Object { value: "e", done: false }
stringIterator.next()       // Object { value: "l", done: false }
stringIterator.next()       // Object { value: "l", done: false }
stringIterator.next()       // Object { value: "o", done: false }
stringIterator.next()       // Object { value: undefined, done: true }

We just proved that String implements both iterable and iterator protocol. Many constructs (for..of, spread, destructuring, yield, etc.) implements iteration protocol under the hood. You can try the same thing with other data types and the result will be similar.

const map = new Map()
map.set('a', 1)
map.set('b', 2)
const mapIterator = map[Symbol.iterator]()
[...mapIterator]

Custom iteratation protocol

So basically my object should have a property Symbol.iterator which is a method and should return me an Object which should have a next method in it, calling which should give me a done and value property? That shouldn't be hard to create.

Turns out, it isn't. 😄

const customIteratationProtocol = (start, end) => ({
    [Symbol.iterator]: () => {
        let startIndex = start;
        return {
            next: () => {
                if(startIndex !== end){
                    return {
                        value: startIndex += 1,
                        done: false
                    }
                }
                return {
                    done: true
                }
            }
        }
    }
});

const customIteratationProtocolInstance = customIteratationProtocol(1, 3);
const customIterationProtocolObj = customIteratationProtocolInstance[Symbol.iterator]()
customIteratationProtocolInstance.next();  // Object { value: 2, done: false }
customIteratationProtocolInstance.next();  // Object { value: 3, done: false }
customIteratationProtocolInstance.next();  // Object { done: true }

You can also implement either of iterable protocol or iterator protocol but that is generally not advisable as it might throw a run-time error if such an object is consumed by a construct which expects an iterable. An object which implements iterable protocol but does not implement iterator protocol is known as non-well-formed iterables.

Generators

Generators in JavaScript are a special kind of function whose execution is not continuous. They allow you to create an internal state in the function construct. The value from this function is returned only when it comes across a yield keyword. Generators are defined by function* syntax. Generator function can be instantiated n number of times but each instantiated object can iterate over the generator only once. You can't use generators with arrow functions though.

function* myGenerator(n) {
    let index = n;
    while(true) {
        yield index += 1;
    }
}
const myGeneratorObj = myGenerator(2);
myGeneratorObj.next().value;      // 3
myGeneratorObj.next().value;      // 4
myGeneratorObj.next().value;      // 5

Are generators really useful? 😕

Although iterators are a great concept of JavaScript engine, I personally never had to use generators in JavaScript. Also in a prototypical language such as JavaScript, I really do not understand the use case which ES6 generators tries to solve. In my opinion, generators bring a lot of complexity to the language because of the following reasons:

  1. It creates a constructor
  2. It then creates a method under that constructor
  3. The value is finally inside the object of that method call

This creates a performance overhead and introduces lots of throwaway stuff. I think we can do away with generators by introducing a simple function factory. The above example can be rewritten as

const myGenerator = n => {
    let index = n;
    return () => index += 1;
}
const gen = myGenerator(2);
gen();      // 3
gen();      // 4
gen();      // 5

Conclusion

JavaScript has a lots of things going under its hood. Iterations is just one of them. If you would like to learn more about iterators and generators, I would recommend going through the official MDN docs. I would love to hear from you what you think about this post. Also if there is a particular use case which generator solved for you I would love to hear that as well. Happy coding! 😁

Top comments (5)

Collapse
 
blackmamba profile image
The Black Mamba🔥

It was an interesting read :-)

Collapse
 
venkteshv profile image
VenkteshV

Generators are really useful when handling side effects . Redux saga is a very good example . Nice article

Collapse
 
varundey profile image
Varun Dey

I did not know that. I'll be sure to check it out. Thanks :)

Collapse
 
bugb profile image
bugb

async await is built in top of generator

Collapse
 
paddy3118 profile image
Paddy3118

That title could have been about Python a decade or two ago.
I smiled.