EcmaScript 6 Features 3: Generators!
Sorry this didn't come out last week - there is a lot to generators, and I wanted to cover as much as I could. This post took quite a while to put together! Anyway, lets get into the subject of this post: Generators! Generators are a special type of function that return multiple values through the yield
keyword (we will get into this in a minute). They can maintain an internal state and return as many values as you like (even an infinite number!). Here is a very simple generator:
function *first_test()
{
yield "Hello!";
yield "I am a generator!";
yield "This is the last thing I will yield.";
}
(gist: todo)
Note the asterisk (*
) at the beginning of the function name. This is the bit that tells the javascript runtime that the function we are defining is a generator, and not an ordinary function. The next question is how do we actually call the generator? This is actually quite simple:
var test = first_test();
console.log(test.next().value);
console.log(test.next().value);
console.log(test.next().value);
Firstly, we need to create an instance of the generator. This is done by just calling the function normally and storing the result. The technical name for the result we store here is an iterator
. To actually extract values from our new generator iterator, we call the next()
function on the instance that we created. This returns an object, which contains 2 things:
- The next value that the generator
yield
s - Whether the generator is finished
The above isn't very elegant - we can do much better than:
var test = first_test(),
next = test.next();
do {
console.log(next.value);
next = test.next();
} while(!next.done);
The above stores the last yield
ed result in a variable, outputs the value to the console, and grabs the next result until the generator has finished. Note that if you try to get the next value when the generator has finished, the value
will be undefined
.
But wait! There is an even better solution to this: a for..of
loop.
for(var str of first_test())
{
console.log(str);
}
The above utilises special for
loop construct to create an instance of our generator (remember this is called in iterator
) and loop over it's return values until it is finished, all in one! Neat or what?!
This is cool, but generators get better. You could use one to generate IDs:
function *get_id(start)
{
var current = start;
while(true)
{
yield start.toString(16);
start++;
}
}
var id_generator = get_id(100);
for(var i = 0; i < 10; i++)
{
console.log(id_generator.next());
}
In the above example I am taking advantage of the fact that you can pass parameters to a generator upon creation like any normal function to set the first ID. This Will produce the following:
next id: 194
next id: 195
next id: 196
next id: 197
next id: 198
next id: 199
next id: 19a
next id: 19b
next id: 19c
next id: 19d
Also, this id generator will never complete, so we will always have a source of unique IDs for our program to use.
You can also pass in a single argument when you call next()
too. In this way we can create a fancy counter:
"use strict";
function *counter(base)
{
var number = base;
while(true)
{
number += yield number;
}
}
var generator = counter(100);
for(var i = 0; i < 10; i++)
{
let next = generator.next(Math.floor(Math.random() * 10))
console.log(next.value);
}
The above generator keeps a counter variable and increments it by the amount that is passed in when the next()
function is called. In this case, we are passing in a random number between 0 and 10. It might output something like this:
100
101
104
112
114
114
122
125
134
134
This could be useful for maintaining an internal state while serving HTTP requests with Node / io.js perhaps? The possibilities are endless...!
This post was put together with the help of this tutorial: ES6 Generators - davidwalsh.name. This is a really good and thorough tutorial on ES6 generators.
By the looks of the tutorial I followed (link above!), we have only just scratched the surface of ES6 generators. I might have to write another blog about them...
The code I wrote while learning about generators and putting this post together can be found here: https://gist.github.com/sbrl/98cd9bde4d06da5ad5ec