// read two pages with it1:
it1
.
next
();
// { value: "Twinkle, twinkle, little bat!", done: false }
it1
.
next
();
// { value: "How I wonder what you're at!", done: false }
// read one page with it2:
it2
.
next
();
// { value: "Twinkle, twinkle, little bat!", done: false }
// read another page with it1:
it1
.
next
();
// { value: "Up above the world you fly,", done: false }
In this example, the two iterators are independent, and iterating through the array on
their own individual schedules.
The Iteration Protocol
Iterators, by themselves, are not that interesting: they are plumbing that supports
more interesting behavior. The iterator protocol enables any object to be iterable.
Imagine you want to create a logging class that attaches timestamps to messages.
Internally, you use an array to store the timestamped messages:
class
Log
{
constructor
() {
this
.
messages
=
[];
}
add
(
message
) {
this
.
messages
.
push
({
message
,
timestamp
:
Date
.
now
() });
}
}
So far, so good…but what if we want to then iterate over the entries in the log? We
could, of course, access
log.messages
, but wouldn’t it be nicer if we could treat
log
as
if it were directly iterable, just like an array? The iteration protocol allows us to make
this work. The iteration protocol says that if your class provides a symbol method
Symbol.iterator
that returns an object with iterator behavior (i.e., it has a
next
method that returns an object with
value
and
done
properties), it is then iterable!
Let’s modify our
Log
class to have a
Symbol.iterator
method:
class
Log
{
constructor
() {
this
.
messages
=
[];
}
add
(
message
) {
this
.
messages
.
push
({
message
,
timestamp
:
Date
.
now
() });
}
[
Symbol
.
iterator
]() {
return
this
.
messages
.
values
();
}
}
The Iteration Protocol | 177