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.
Generators as Iterators
We can use generator functions as iterators.
For instance, we can write:
function* genFn() {
yield 'foo';
yield 'bar';
yield 'baz';
}
const gen = genFn();
console.log(gen.next());
console.log(gen.next());
to create a generator function and a generator.
genFn
is the generator function as indicated with the function*
keyword.
The function returns a generator with the genFn
call.
Then we call next
to get the next item on the list.
The yield
keyword lets us return the value with the next
method call.
Therefore, we get:
{value: "foo", done: false}
{value: "bar", done: false}
to get the yielded items via the value
property.
We can also access the yielded values with the for-of loop.
For example, we can write:
function* genFn() {
yield 'foo';
yield 'bar';
yield 'baz';
}
const gen = genFn();
for (const x of gen) {
console.log(x);
}
Then we get:
foo
bar
baz
logged.
The for-of loop can access the values from a generator.
The spread operator lets us convert a generator to an array by extracting the yielded values and putting it there:
function* genFn() {
yield 'foo';
yield 'bar';
yield 'baz';
}
const gen = genFn();
const arr = [...gen];
And destructuring can also work with generators by assigning the yielded values as values of the variables on the left side:
function* genFn() {
yield 'foo';
yield 'bar';
yield 'baz';
}
const [x, y] = genFn();
Then we get that x
is 'foo'
and y
is 'bar'
.
Returning from a Generator
We can use the return
statement from a generator.
For instance, we can write:
function* genFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
Then we can invoke our generator by writing:
const gen = genFn();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
From the console log, we can see that the values are:
{value: "foo", done: false}
{value: "bar", done: false}
{value: "baz", done: true}
The return
statement set done
to true
.
However, most other const5ructurs that work with iterable objects ignore the return value.
For instance, if we have the for-of loop:
function* genFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
const gen = genFn();
for (const x of gen) {
console.log(x);
}
Then we get:
foo
bar
logged.
And if we have the spread operator:
function* genFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
const gen = genFn();
const arr = [...gen];
Then arr
is [“foo”, “bar”]
.
Throwing Exception from a Generator
We can throw exceptions from a generator.
For instance, we can write:
function* genFn() {
throw new Error('error');
}
const gen = genFn();
console.log(gen.next())
We have a generator function that throws an error.
When we call it and then call next
on the returned generator, we see the error logged in the console.
Conclusion
We can return items and throw errors in a generator function.
They can also produce values we can use.