A generator is a process that can be paused and resumed and can yield multiple values. A generator in JavaScript consists of a generator function, which returns an iterable Generator object.
In other words, we can say that generators
can be used to manage flow control in a code. Cancel asynchronous operations
easily since execution can be paused anytime.
Regular
functions return only one, single value (or nothing).
Generators
can return (“yield”) multiple values, one after another, on-demand. They work
great with iterables, allowing creating
data streams with ease.
Generator functions
To create a
generator, we need a special syntax construct: function*
, so-called “generator function”.
It looks like
this:
function*generateSequence()
{
yield
1;
yield
2;
return
3;
}
Generator
functions behave differently from regular ones. When such a function is called,
it doesn’t run its code. Instead, it returns a special object, called “generator
object”, to manage the execution.
function*generateSequence()
{
yield
1;
yield
2;
return
3;
}
// "generator function" creates "generator object"
letgenerator
=generateSequence();
alert(generator); // [object Generator]
The function
code execution hasn’t started yet:
The main method
of a generator is next()
. When called, it runs the execution until the
nearest yield <value>
statement (value
can be omitted, then it’s undefined
). Then the
function execution pauses, and the yielded value
is returned to the outer code.
The result of next()
is always an object with two properties:
·
value
: the yielded
value.
·
done
: true
if the function code has finished, otherwise false
.
For instance,
here we create the generator and get its first yielded value:
function*generateSequence()
{
yield
1;
yield
2;
return
3;
}
letgenerator
=generateSequence();
let one = generator.next();
alert(JSON.stringify(one
));// {value: 1, done: false}
As of now, we
got the first value only, and the function execution is on the second line:
Let’s call generator.next()
again. It resumes the code execution and
returns the next yield
:
lettwo
=generator
.next();
alert(JSON.stringify(two
));// {value: 2, done: false}
And, if we
call it a third time, the execution reaches the return
a statement that finishes the function:
letthree
=generator
.next();
alert(JSON.stringify(three
));// {value: 3, done: true}
Now the a generator is done. We should see it from done:true
and process value:3
as the final result.
function* f(…)
or function *f(…)
?
Both syntaxes
are correct.
But usually
the first syntax is preferred, as the star *
denotes that it’s a generator function, it
describes the kind, not the name, so it should stick with the function
keyword.
Generators are iterable
As you
probably already guessed looking at the next()
a method, generators are iterable.
We can loop
over their values using for..of
:
function*generateSequence()
{
yield
1;
yield
2;
return
3;
}
letgenerator
=generateSequence();
for(letvalue
ofgenerator
){
alert(
value
);// 1, then 2
}
Looks a lot
nicer than calling .next().value
, right?
…But please
note: the example above shows 1
, then 2
, and that’s
all. It doesn’t show 3
!
It’s because for..of
iteration ignores the last value
, when done: true
. So, if we
want all results to be shown by for..of
, we must
return them with yield
:
function*generateSequence()
{
yield
1;
yield
2;
yield 3;
}
letgenerator
=generateSequence();
for(letvalue
ofgenerator
){
alert(
value
);// 1, then 2, then 3
}
As generators
are iterable, we can call all related functionality, e.g. the spread syntax ...
:
function*generateSequence()
{
yield
1;
yield
2;
yield
3;
}
letsequence
=[0,
...generateSequence()];
alert(sequence
);// 0, 1, 2, 3
In the code
above, ...generateSequence()
turns the iterable generator object into an
array of items (read more about the spread syntax in the chapter Rest parameters and spread syntax)
Generator composition
Generator the composition is a special feature of generators that allows to transparently
“embed” generators in each other.
For instance,
we have a function that generates a sequence of numbers:
function*generateSequence(start, end)
{
for
(let
i
=start
;i
<=end
;i
++)yield
i
;
}
Now we’d like
to reuse it to generate a more complex sequence:
·
first, digits 0..9
(with character codes 48…57),
·
followed by uppercase alphabet letters A..Z
(character codes 65…90)
·
followed by lowercase alphabet letters a..z
(character codes 97…122)
We can use
this sequence e.g. to create passwords by selecting characters from it (could
add syntax characters as well), but let’s generate it first.
In a regular
function, to combine results from multiple other functions, we call them, store
the results, and then join at the end.
Summary
·
Generators are created by generator functions function*
f(…) {…}
.
·
Inside generators (only) there exists a yield
operator.
·
The outer code and the generator may exchange results via next/yield
calls.
In modern
JavaScript, generators are rarely used. But sometimes they come in handy because the ability of a function to exchange data with the calling code during
the execution is quite unique. And, surely, they are great for making iterable
objects.
No comments:
Post a Comment