The iterable protocol is underrated. I don't see people using it, which makes me sad because it's a wonderful instrument. It defines how we loop over a specific object. Or in other words, it allows us to create custom iterating behavior. We have to create a property called @@iterator
(Symbol.iterator
is a shortcut for this key). That property must equal a zero-argument function that returns an object matching the iterable protocol. Here is an example:
const user = {
data: ["JavaScript", "HTML", "CSS"],
[Symbol.iterator]: function () {
let i = 0;
return {
next: () => ({
value: this.data[i++],
done: i > this.data.length,
}),
};
},
};
for (const skill of user) { console.log(skill); }
The protocol requires that we return an object with a next
method. That method should result in another object that has value
and done
fields. The value
could be anything, and done
is a boolean that indicates whether the iteration is over.
Notice, in the example above, how user
is not an array, but we can use it as such. That is because we defined a custom iterator. The script outputs "JavaScript"
, "HTML"
and "CSS"
(in that order).
This feature comes in handy if we have complex data structures and we want to access deeply nested properties. I like to use the iterable protocol to facilitate the destructing of my objects.
const user = {
name: { first: 'Krasimir', last: 'Tsonev' },
position: 'engineer',
[Symbol.iterator]: function () {
let i = 0;
return {
next: () => {
i++;
if (i === 1) {
return { value: `${this.name.first} ${this.name.last}`, done: false };
} else if (i === 2) {
return {value: this.position, done: false };
}
return { done: true }
}
}
}
}
const [name, position] = user;
console.log(`${name}: ${position}`); // Krasimir Tsonev: engineer
By definition, we can destruct every iterable object. And if our objects are not iterable, we can make them so by using the technique above - defining an iterable protocol.