Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at JavaScript generators.
The yield Keyword
The yield
keyword can only be used in generator functions.
For example, we can write:
function* genFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
But we can’t write:
function* genFn() {
['foo', 'bar'].forEach(x => yield x);
}
We’ll get a syntax error.
Recursion with yield*
The yield*
keyword lets us call another generator function within a generator function.
For instance, we can write:
function* genFn() {
yield 'foo';
yield 'bar';
yield 'baz';
}
function* bar() {
yield 'x';
yield* genFn();
yield 'y';
}
const gen = bar()
to call the genFn
generator function within the bar
generator function.
So if we use it by writing:
const arr = [...gen];
then arr
is [“x”, “foo”, “bar”, “baz”, “y”]
.
Calling genFn
returns an object but it doesn’t run genFn
.
The yield*
keyword runs the generator function when it’s time for its items to be yielded.
yield*
Considers End-of-Iteration Values
yield*
ignores return
values.
For example, if we have:
function* genFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
function* bar() {
yield 'x';
yield* genFn();
yield 'y';
}
const gen = bar()
const arr = [...gen];
Then arr
is [“x”, “foo”, “bar”, “y”]
, yield*
skipped over 'baz'
since it’s returned instead of yield.
We can use yield*
to recursively call generator functions.
So we can easily use it to return items from a tree structure.
For instance, we can create a Tree
class with various branch nodes:
class Tree {
constructor(value, left = null, center = null, right = null) {
this.value = value;
this.left = left;
this.center = center;
this.right = right;
}
*[Symbol.iterator]() {
yield this.value;
if (this.left) {
yield* this.left;
}
if (this.center) {
yield* this.center;
}
if (this.right) {
yield* this.right;
}
}
}
left
, center
, and right
are all generator functions created from the Tree
class.
So we can write:
const tree = new Tree('a',
new Tree('b',
new Tree('c'),
new Tree('d'),
new Tree('e')),
new Tree('f'));
for (const x of tree) {
console.log(x);
}
And we get:
a
b
c
d
e
f
The value
is the value for the tree node itself.
And we populate the nodes with the Tree
constructor.
Generators as Observers
Generators can also be data observers.
We use it to send values with the next
method.
The next
method keeps returning values from then generator until the values run out.
For instance, if we have:
function* genFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
const gen = genFn();
console.log(gen.next());
And we get:
{value: "foo", done: false}
We can also call next
with an argument to return that index of what’s yielded with the generator.
For example, we can write:
function* genFn() {
console.log(yield);
console.log(yield);
console.log(yield);
}
const gen = genFn();
console.log(gen.next('a'));
console.log(gen.next('b'));
console.log(gen.next('c'));
We use the yield
keyword to get the value passed in from the argument we passed into next
.
The value return from the next
method is:
{value: undefined, done: false}
value
is undefined
and done
is false
.
yield
keyword without an operand gets the value from the next
method.
Conclusion
The yield
and yield*
keyword have many uses.
yield
can return values and also takes them from the next
method.