What is function*
in JavaScript?
In JavaScript, function*
defines a generator function.
But what are generator functions,
and what are they useful for?
As an artificial task,
say you want to print the next Fibonacci number
every time the user presses a button,
like this:
Since the mid-90s, you could implement this in the browser with something like:
<button onclick="showNextFib()">Show next</button>
<span id="fib"></span>
<script>
const fibEl = document.getElementById("fib");
let x = 1, y = 2;
function showNextFib() {
fibEl.innerText = x;
const z=x+y;
x = y;
y = z;
}
showNextFib();
</script>
Depending on who you speak to, this code has some problems. It has global state, so you can only have one Fibonacci sequence in your program. And it mixes up the abstract concept of Fibonacci numbers with the display of those numbers. You could solve these problems by using a JavaScript iterator to generate the Fibonacci numbers:
function FibIterator() {
this.state = { x: 1, y: 2 };
this.next = function() {
const ret = this.state.x;
const z = this.state.x + this.state.y;
this.state.x = this.state.y;
this.state.y = z;
return { done: false, value: ret };
}
};
const fibs = new FibIterator();
function showNextFib() {
fibEl.innerText = fibs.next().value;
}
Above, the fibs
object is defined with an internal state
plus a next
function which steps from one state to the next,
and returns part of that state.
This is the general pattern of a JavaScript iterator.
Now we finally get to generators!
JavaScript, as of 2015,
gives you a fundamentally new way to implement FibIterator
:
as a generator function.
It looks like this:
function* FibIterator() {
let x = 1, y = 2;
for (;;) {
yield x;
const z=x+y;
x = y;
y = z;
}
}
const fibs = FibIterator();
There are two new keywords here, function*
and yield
.
The function*
keyword lets you declare a generator function
instead of a normal function.
When you call a generator function, like FibIterator()
,
the function body is not actually executed,
and you do not get back that function’s return value.
Instead, you get back an iterator.
You can think of the state of the iterator fibs
as containing:
- The state of the local variables in that function call.
In our
FibIterator
example,fibs.localVars
could bex = 3; y= 5
. - A program counter.
This points to one of the
yield
expressions in the function body (or to the start of the function body, if.next()
hasn’t been called yet). In ourFibIterator
example,fibs.programCounter
could point to theyield x
expression.
When the UI component of our program calls fibs.next()
,
the runtime executes the function body of fibs
,
starting at fibs.ProgramCounter
,
with the local variables set to fibs.localVars
.
If the execution hits a yield x
expression,
the fibs.next()
call returns { done: false, value: x }
.
If the execution instead hits a return x
expression,
the fibs.next()
call returns { done: true, value: x }
.
This mechanism lets us write FibIterator
with an infinite loop,
using for (;;)
or while (true)
.
Normally in JavaScript,
infinite loops will lead to your program freezing
and your computer getting hot,
because nothing else can happen until that loop terminates.
But the yield
expression lets you jump out of that infinite loop
to make progress on other useful things,
like drawing a new number to the screen and waiting for more input.
Now you’ve seen how JavaScript generators can be used to generate data. But, surprise, generators can also be used to consume data! Read my next post to find out how ...
This page copyright James Fisher 2019. Content is not associated with my employer.