Learn more about Israeli war crimes in Gaza, funded by the USA, Germany, the UK and others.

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:

  1. The state of the local variables in that function call. In our FibIterator example, fibs.localVars could be x = 3; y= 5.
  2. 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 our FibIterator example, fibs.programCounter could point to the yield 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 ...

Tagged #programming, #javascript.

Similar posts

More by Jim

Want to build a fantastic product using LLMs? I work at Granola where we're building the future IDE for knowledge work. Come and work with us! Read more or get in touch!

This page copyright James Fisher 2019. Content is not associated with my employer. Found an error? Edit this page.