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 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
done
istrue
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.
- becuase it has
- Iterator protocol
- because iterable protocol returns an
Object
which has a methodnext
in 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.iterator
which is a method and should return me anObject
which should have anext
method in it, calling which should give me adone
andvalue
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:
- 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.