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
Stringisn't everything here an implementation ofObject?
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
doneistrue
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.iteratorin it's __proto__ chain.
- becuase it has
- Iterator protocol
- because iterable protocol returns an
Objectwhich has a methodnextin it.
- because iterable protocol returns an
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.iteratorwhich is a method and should return me anObjectwhich should have anextmethod in it, calling which should give me adoneandvalueproperty? 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:
- It creates a constructor
- It then creates a method under that constructor
- 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)
It was an interesting read :-)
Generators are really useful when handling side effects . Redux saga is a very good example . Nice article
I did not know that. I'll be sure to check it out. Thanks :)
async await is built in top of generator
That title could have been about Python a decade or two ago.
I smiled.