function*
rainbow
() {
// the asterisk marks this as a generator
yield
'red'
;
yield
'orange'
;
yield
'yellow'
;
yield
'green'
;
yield
'blue'
;
yield
'indigo'
;
yield
'violet'
;
}
Now let’s see how we call this generator. Remember that when you call a generator,
you get back an iterator. We’ll call the function, and then step through the iterator:
const
it
=
rainbow
();
it
.
next
();
// { value: "red", done: false }
it
.
next
();
// { value: "orange", done: false }
it
.
next
();
// { value: "yellow", done: false }
it
.
next
();
// { value: "green", done: false }
it
.
next
();
// { value: "blue", done: false }
it
.
next
();
// { value: "indigo", done: false }
it
.
next
();
// { value: "violet", done: false }
it
.
next
();
// { value: undefined, done: true }
Because the
rainbow
generator returns an iterator, we can also use it in a
for...of
loop:
for
(
let
color
of
rainbow
()) {
console
.
log
(
color
)
:
}
This will log all the colors of the rainbow!
yield Expressions and Two-Way Communication
We mentioned earlier that generators allow two-way communication between a gen‐
erator and its caller. This happens through the
yield
expression. Remember that
expressions evaluate to a value, and because
yield
is an expression, it must evaluate
to something. What it evaluates to are the arguments (if any) provided by the caller
every time it calls
next
on the generator’s iterator. Consider a generator that can carry
on a conversation:
function*
interrogate
() {
const
name
=
yield
"What is your name?"
;
const
color
=
yield
"What is your favorite color?"
;
return
`
${
name
}
's favorite color is
${
color
}
.`
;
}
When we call this generator, we get an iterator, and no part of the generator has been
run yet. When we call
next
, it attempts to run the first line. However, because that
line contains a
yield
expression, the generator must yield control back to the caller.
The caller must call
next
again before the first line can resolve, and
name
can receive
180 | Chapter 12: Iterators and Generators