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
andplayed
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.