Iterables Javascript ES6

Yoel Macia
The Javascript Adventure
4 min readDec 15, 2019

The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for..of construct. In order to be iterable, an object must implement the @@iterator method.

Photo by Tim Johnson on Unsplash

Let´s talk about we have the next structure,

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
}

How we can print the songs?

The first option I think about is to use a loop, for example,

for (let key in songsFromYoel) {
const keys = songsFromYoel[key];
for (let key2 in keys) {
for (let key3 in keys[key2]) {
console.log(keys[key2][key3]);
}
}
}
// Output
Bohemian Rhapsody
Smoke on the water
Walk
Enter Sandman

This is a little confusing and exhausting.

How about use a .foreach() instead,

Object.keys(songsFromYoel).forEach(key => {
Object.keys(songsFromYoel[key]).forEach(key2 => {
songsFromYoel[key][key2].forEach(key3 => {
console.log(key3);
});
});
});
// Output
Bohemian Rhapsody
Smoke on the water
Walk
Enter Sandman

Same result, I get lost with the keys, it’s chaotic.

The simplest option that I use for this type of structures is,

Iterables

When introducing generators we have to talk about two concepts.

  1. Use of the for… of
  2. Implement a method [Symbol.iterator]()

Use of the for… of

The for…of statement creates a loop iterating over iterable objects, including:

String, Array, arguments ,NodeList, TypedArray, Map, Set.

The for…of loop unlike the well-known for…in , is for looping over data—like the values in an array.

The benefit of using for … of

  • Works with break, continue and return, unlike .forEach().

So let´s implement it in our example.

for (index of songsFromYoel) {
console.log(index);
}
// Output
songsFromYoel is not iterable

Why are you throwing us a mistake?

Because we have not implemented the [Symbol.iterator]().

Implement a method [Symbol.iterator]()

[Symbol.iterator]() returns a new iterator object and it handles the further iteration process.

This object have one method,

next() -> Returns an object {done: Boolean, value: any} containing the next value in the iterator. If all items have been returned, done will be true.

So let´s create our [Symbol.iterator](),

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
},
[Symbol.iterator]() {
return {
next() {}
};
}
};

But this doesn’t work because we need to convert our allsongs object into an array to iterate it.

As we do it because first we convert it into an array with the values, with the object.values() method.

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
},
[Symbol.iterator]() {
let allSongs = Object.values(this.allsongs);
return {
next() {}
};
}
};

Now we have to create an index and increase it by the elements that we have inside this array.

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
},
[Symbol.iterator]() {
let allSongs = Object.values(this.allsongs);
let allsongsIndex = 0;
return {
next() {
return {
value: allSongs[allsongsIndex],
done: false
};

}
};
}
};
// Output
[ 'Bohemian Rhapsody', 'Smoke on the water' ]
[ 'Bohemian Rhapsody', 'Smoke on the water' ]
[ 'Bohemian Rhapsody', 'Smoke on the water' ]
...

Now we have to tell the infinite loop when to stop, for that we create an if, if the index we just created is greater than the length of our array, we send that the iteration stop with an object { value: undefined, done: true }.

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
},
[Symbol.iterator]() {
let allSongs = Object.values(this.allsongs);
let allsongsIndex = 0;
return {
next() {
if (allsongsIndex >= allSongs.length) {
return { value: undefined, done: true };
}


return {
value: allSongs[allsongsIndex],
done: false
};
}
};
}
};

But it still fails, because it doesn’t show us the second elements of the array.

Because we need to create a new index to iterate in that second array and increase the allsongsIndex.

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
},
[Symbol.iterator]() {
let allSongs = Object.values(this.allsongs);
let allsongsIndex = 0;
let songsfromYoelIndex = 0;
return {
next() {
if (songsfromYoelIndex >= allSongs[allsongsIndex].length){
allsongsIndex++;
songsfromYoelIndex = 0;
}
if (allsongsIndex >= allSongs.length) {
return { value: undefined, done: true };
}

return {
value: allSongs[allsongsIndex],
done: false
};
}
};
}
};

And we need to add the new index to the return value.

const songsFromYoel = {
allsongs: {
rock: ["Bohemian Rhapsody", "Smoke on the water"],
metal: ["Walk", "Enter Sandman"]
},
[Symbol.iterator]() {
let allSongs = Object.values(this.allsongs);
let allsongsIndex = 0;
let songsfromYoelIndex = 0;
return {
next() {
if (songsfromYoelIndex >= allSongs[allsongsIndex].length){
allsongsIndex++;
songsfromYoelIndex = 0;
}
if (allsongsIndex >= allSongs.length) {
return { value: undefined, done: true };
}

return {
value: allSongs[allsongsIndex][songsfromYoelIndex++],
done: false
};
}
};
}
};
// Output
Bohemian Rhapsody
Smoke on the water
Walk
Enter Sandman

--

--

Yoel Macia
The Javascript Adventure

Writing daily about Javascript. Creator of the publication The Javascript Adventure.