Check out our Bootcamp Schedule View Schedule

Generators: Rick Astley and the Sequence of Fibonacci

Chris Aquino

On the internet, there are two things that are totally played out: Rick Astley and
the Fibonacci sequence. But finally, in one blog post, they have been combined. Well. Sort of.

This post is an introduction to JavaScript’s generators, added in ES6, the latest version of JavaScript. This update is also known as “ES2015.”

What is This… Generator?

In a nutshell, generators are functions that can be paused
and resumed. They are useful for things like incrementally
creating very large sequences.

More importantly, they create sequences that are iterators.

The Way of Iteration

If you’ve worked with arrays, you have likely had to iterate
through the elements of that array. There are a few ways you can do this in JavaScript.

One way is to use the very boring (but very fast) for-loop:

let numbers = [0, 1, 1, 2, 3, 5, 8, 13];
for (let index=0; index < numbers.length; index++) {
  console.log(`This number is: ${numbers[index]}`);
}

That snippet of code prints out the first few Fibonacci numbers to the console, as you might expect.

Another way to go through the elements is to use the forEach method
on an array. When you pass a function argument to forEach, forEach will visit each element in the array. As it visits each element, it invokes your
function argument and passes it that element.

let numbers = [0, 1, 1, 2, 3, 5, 8, 13];
numbers.forEach((number) => {
  console.log(`This number is: ${number}`);
});

Here, we’ve passed an anonymous arrow function to forEach, but you can also use named functions or regular (non-arrow) anonymous functions. The arrow function expects
to receive the current item being visited by forEach and gives it the
label number, which is then used inside the body of the arrow function.
Some developers prefer forEach because they find it more expressive than
a for-loop.

Iterating using the for..of syntax

ES6 provides another way of doing iteration: by using for..of.

let numbers = [0, 1, 1, 2, 3, 5, 8, 13];
for (let number of numbers) {
  console.log(`This number is: ${number}`);
}

Like a for-loop, you use the for keyword. But inside of the parentheses, you specify a variable that will point to the current element. Inside the curly braces, you write the body as usual.

Ok, great! Yet another way to work with array elements. Admittedly, it’s
not all that exciting. But, for..of isn’t limited to arrays. You can
create custom iterators that work with for..of.

Building a Custom Iterator

There are two new pieces of syntax that let you create custom iterators:
function * and yield.

Here’s how you would write our not-so-exciting example using that syntax:

function * numbers() {
  yield 0;
  yield 1;
  yield 1;
  yield 2;
  yield 3;
  yield 5;
  yield 8;
  yield 13;
}

for (let number of numbers()) {
  console.log(`This number is: ${number}`);
}

First, you declare a function using the function * syntax.
This tells the JavaScript engine that this function will generate
an iterator. You could say that this is a…generator function.

Some developers prefer to put the * right beside the
function name, like so:

function *numbers() {
  yield 0;
  yield 1;
  yield 1;
  yield 2;
  yield 3;
  yield 5;
  yield 8;
  yield 13;
}

(That is the style that will be used for the rest of this blog post.)

Inside of your generator, you yield at least one value.

When you call a generator, it returns an iterator.
for..of knows to “ask” an iterator for its next value.
The iterator yields its values until there are no more yield
statements.

Obviously, you wouldn’t want to use this syntax to spit
out a bunch of values you could just put into an array.
Let’s continue with the tried and true Fibonacci sequence in the next example.

“Hello, Fibonacci!”

One of the ways you might use generators is to create a sequence
of calculated values. Here is a generator that produces a
sequence of Fibonacci numbers.

function *fibonacci() {
  var n1 = 0;
  var n2 = 1;
  while(true) {
    yield n1; // yield the first number.

    // Calculate the next Fibonacci number
    [n1, n2] = [n2, n1 + n2]; // Destructuring assignment!
  }
}

You might think that calling fibonacci would try to produce
an array of infinite length. But, it doesn’t. It only does enough work to prepare the next value in the sequence.

Notice that the yield is inside of a while(true) statement.
Generators pause their execution until the next time that for..of asks
for another value.

Of course, if you try to iterate over fibonacci with for..of, it will
run forever unless you break out of it. Or, your browser will simply
crash after a few seconds:

Chrome trying to print an infinite sequence

Iterators, Behind the Curtain

So, to recap: generator functions return iterators. But how do iterators work? What exactly is for..of doing with an iterator to get those values out?

To answer that, consider the following generator:

function *roll() {
  yield "Never gonna give you up";
  yield "Never gonna let you down";
  yield "Never gonna run around and desert you";
  yield "Never gonna make you cry";
  yield "Never gonna say goodbye";
  yield "Never gonna tell a lie and hurt you";
}

As mentioned earlier, to get the iterator, simply call the generator.

let rick = roll();

To get the next value, invoke the next function on the iterator,
which will cause it to yield its first value.

rick.next();
// {value: "Never gonna give you up", done: false}

Notice that the value returned is an object with two properties, value and done.
The value property is what the iterator yielded. The done property signals
whether or not there are more values.

If you continue to call rick.next(), you’ll find that the iterator is eventually
drained of its values:

...
rick.next();
// {value: "Never gonna say goodbye", done: false}

rick.next();
// {value: "Never gonna tell a lie and hurt you", done: false}

rick.next();
// {value: undefined, done: true}

When an iterator has no more values to yield, the value property is undefined, while the done property is now true.

Knowing how an iterator works, you could write a simple implementation of for..of
that uses callbacks:

function forOf(iter, callback) {
  let result = iter.next();
  while (!result.done) {
    callback(result.value);
    result = iter.next();
  }
}

And that function could be used like so:

forOf(roll(), (val) => { console.log(val); });

Result of calling our forOf function

Iterable Iterators and Potent Potables

Generators (things that produces iterators) and iterators
(things that can be iterated over) work based on two separate protocols: the iterable protocol and the
iterator protocol.

A generator function is really just syntactic sugar
for implementing the iterable protocol. This protocol
specifies that an object must have a method named
[Symbol.iterator]. (That’s a method whose name
is the ES6 constant Symbol.iterator.)

This [Symbol.iterator] method can return any object, as long
as it is an iterator, meaning that it implements the iterator protocol.

The iterator protocol says that an iterator needs to have
a next method, and that next should return an
object with a value property and a boolean done property.

Here is an example of an object literal version of our
generator function.

let astley = {
  [Symbol.iterator]() {
    return this;
  },
  values: [
    {
      value: "Never gonna give you up",
      done: false
    },
    {
      value: "Never gonna let you down",
      done: false
    },
    {
      value: "Never gonna run around and desert you",
      done: false
    },
    {
      value: "Never gonna make you cry",
      done: false
    },
    {
      value: "Never gonna say goodbye",
      done: false
    },
    {
      value: "Never gonna tell a lie and hurt you",
      done: false
    },
    {
      value: undefined,
      done: true
    }
  ],
  next() {
    return this.values.shift();
  }
};

The [Symbol.iterator] method simply returns a reference to itself, since
it also implements the next method. [Symbol.iterator] is not required
to return this — you are free to make your iterables separate from your
iterators. In this example, we made our iterable and iterator the same object.

You could use your iterable object like so:

for (let lalala of astley) {
  console.log(lalala);
}

The for..of construct implicitly calls astley[Symbol.iterator] to get the iterator object. Then it calls the next method over and over until the
value of the done property is true.

The object literal version is much more verbose than our generator function.
But, it is a good option if you already have an object that encapsulates some
sort of computed sequence and you want to use it with for..of.

Summary

Generators were added to JavaScript in ES6. When combined with for..of, they provide an easy way to create and consume sequences of values.

They have good support in evergreen browsers such as Chrome, Firefox and Edge. In older browsers
(e.g., IE < 9 and Safari < 9), you will want to transpile your code using a tool like Babel.

Stay tuned for upcoming posts about generators and iterators, in which we’ll look at error handling, recursion and coroutines.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project