Demystifying ES6 Iterables & Iterators
ES6 introduces a new way to interact with JavaScript data structures — iteration. Let’s demystify it.
There are 2 core concepts:
- Iterable — described by a data structure that provides a way to expose its data to the public. This is done by implementing a method whose key is
Symbol.iterator
.Symbol.iterator
is a factory of iterators. - Iterator — described by a structure that contains a pointer to the next element in the iteration.
Protocol
Both iterable and iterator follow a protocol that enables objects to be iterable:
- An interable must be an object with a function iterator whose key is
Symbol.iterator
.
- An iterator must be an object with a function named
next
that returns an object with the keys:value
— the current item in the iteration; anddone
— true if the iteration has finished, false otherwise.
Iterability
Iterability follows the idea of data sources and data consumers:
- data sources— are the place from where data consumers get their data. For instance, an
Array
such as[1,2,3]
is a data source structure that holds the data through which a data consumer will iterate (e.g.1, 2, and 3
). More examples areString
,Maps
andSets
. - data consumers — are the what consume the data from data sources. For instance, the
for-of
loop is a data consumer able to iterate over anArray
data source. More examples are thespread operator
andArray.from
.
For a structure to be a data source, it needs to allow and say how its data should be consumed. This is done through iterators. Therefore, a data source needs to follow the iterator protocol described above.
However, it’s not practical for every data consumer to support all data sources, especially since JavaScript allows us to build our own data sources. So ES6 introduces the interface Iterable.
Data consumers consume the data from data sources through iterables.
In Practice
Let’s see how this works on a defined data source — Array
.
Iterable Data Sources
We will use for-of
to explore some of the data sources that implement the iterable protocol.
Plain Objects
At this stage we need to say that plain objects are not iterable. Axel Rauschmayer does a great job explaining why on Exploring ES6.
A brief explanation is that we can iterate over a JavaScript objects at two different levels:
- program level — which means iterating over an object properties that represent it’s structure. For instance,
Array.prototype.length
, wherelength
is related with the object’s structure and not it’s data. - data level — meaning iterating over a data structure and extracting its data. For instance, for our
Array
example, that would mean iterating over the array’s data. Ifarray = [1,2,3,4]
, iterate over the values1, 2, 3 and 4
.
However, bringing the concept of iteration into plain objects means mixing-up program and data structures — Axel
The problem with plain objects is everyones’ ability to create their own objects.
In our Hugo’s example how would JavaScript distinguish between the data level, i.e. Hugo.fullName
, and the program level, i.e. Hugo.toString()
?
While it is possible to distinguish between the two levels of iteration on well-defined structures, such as Arrays
, it is impossible to do so for any object.
This is why we get iteration for free on Array
(also on String
, Map
, and Set
) but not on plain objects.
We can, however, implement our own iterables.
Implement Iterables
We can build our own iterables, although we usually use generators for that.
In order to build our own iterable we need to follow the iteration protocol, which says:
- An object becomes an iterable if it implements a function whose key is
Symbol.iterator
and returns aniterator
. - The
iterator
itself is an object with a function callednext
inside it.next
should return an object with two keysvalue
anddone
.value
contains the next element of the iteration anddone
a flag saying if the iteration has finished.
Example
Our iterable implementation is very simple. We have followed the iterable protocol and on each iteration the for-of
loop will ask the iterator for the next
element.
Our iterator will return on next
an object containing the following by iteration:
Please note that we switch the order of the our properties next
and done
for convenience. Having next
first, it would break the implementation since we will first pop an element and then count the elements.
It is useful to know that done
is false
by default, which means that we can ignore it when that’s the case. The same is true for value
when done
is true
.
We will see that in a minute.
Iterator as an Iterable
We could build our iterator as an iterable.
Please note that this is the pattern followed by ES6 built-in iterators.
Why is this a useful?
Although for-of
only works with iterables, not with iterators, being the same means that we can pause the execution of for-of
and continue afterwords.
Return and Throw
There are two optional iterator methods that we haven’t explore yet:
Return
return
gives the iterator the opportunity to clean up the house when it breaks unexpectedly. When we call return
on an iterator we are specifying that we don’t plan to call next
anymore.
Throw
throw
is only applied to generators. We will see that when we play with generators.
Conclusion
ES6 brings iteration as a new way to iterate over JavaScript data structures.
For iteration to be possible there are data producers, who contain the data, and data consumers, who take that data.
In order for this combination to work smoothly iteration is defined by a protocol, which says:
- An
iterable
is an object that implements a function whose key isSymbol.iterator
and returns aniterator
. - An
iterator
is an object with a function callednext
inside it.next
is an object with two keysvalue
anddone
.value
contains the next element of the iteration anddone
a flag saying if the iteration has finished.
Plain objects are not iterable
since there’s no easy way to distinguish between program and data level iteration.
That’s why ES6 offers a way to build our own iterators by following the iterator
protocol.
Thanks to 🍻
- Axel Rauschmayer for his Exploring ES6 — Iteration
- Nicolás Bevacqua for his PonyFoo — ES6 Iterators in Depth
- To all The Simpsons fans
Be sure to check out my other articles on ES6