ES6 Generators and Iterators: a Developer’s Guide
ES6 brought a number of new features to the JavaScript language. Two of these features, generators and iterators, have substantially changed how we write specific functions in more complex front-end code.
While they do play nicely with each other, what they actually do can be a little confusing, so let’s check them out.
Iterators
Iteration is a common practice in programming and is usually used to loop over a set of values, either transforming each value, or using or saving it in some way for later.
In JavaScript, we’ve always had for
loops that look like this:
for (var i = 0; i < foo.length; i++){
// do something with i
}
But ES6 gives us an alternative:
for (const i of foo) {
// do something with i
}
This is arguably way cleaner and easier to work with, and reminds me of languages like Python and Ruby. But there’s something else that’s pretty important to note about this new kind of iteration: it allows you to interact with elements of a data set directly.
Imagine that we want to find out if each number in an array is prime or not. We could do this by coming up with a function that does exactly that. It might look like this:
function isPrime(number) {
if (number < 2) {
return false;
} else if (number === 2) {
return true;
}
for (var i = 2; i < number; i++) {
if (number % i === 0) {
return false;
break;
}
}
return true;
}
Not the best in the world, but it works. The next step would be to loop over our list of numbers and check whether each one is prime with our shiny new function. It’s pretty straightforward:
var possiblePrimes = [73, 6, 90, 19, 15];
var confirmedPrimes = [];
for (var i = 0; i < possiblePrimes.length; i++) {
if (isPrime(possiblePrimes[i])) {
confirmedPrimes.push(possiblePrimes[i]);
}
}
// confirmedPrimes is now [73, 19]
Again, it works, but it’s clunky and that clunkiness is largely down to the way JavaScript handles for
loops. With ES6, though, we’re given an almost Pythonic option in the new iterator. So the previous for
loop could be written like this:
const possiblePrimes = [73, 6, 90, 19, 15];
const confirmedPrimes = [];
for (const i of possiblePrimes){
if ( isPrime(i) ){
confirmedPrimes.push(i);
}
}
// confirmedPrimes is now [73, 19]
This is far cleaner, but the most striking bit of this is the for
loop. The variable i
now represents the actual item in the array called possiblePrimes
. So, we don’t have to call it by index anymore. This means that instead of calling possiblePrimes[i]
in the loop, we can just call i
.
Behind the scenes, this kind of iteration is making use of ES6’s bright and shiny Symbol.iterator() method. This bad boy is in charge of describing the iteration and, when called, returns a JavaScript object containing the next value in the loop and a done
key that is either true
or false
depending on whether or not the loop is finished.
In case you’re interested in this sort of detail, you can read more about it on this fantastic blog post titled Iterators gonna iterate by Jake Archibald. It’ll also give you a good idea of what’s going on under the hood when we dive into the other side of this article: generators.
Generators
Generators, also called “iterator factories”, are a new type of JavaScript function that creates specific iterations. They give you special, self-defined ways to loop over stuff.
Okay, so what does all that mean? Let’s look at an example. Let’s say that we want a function that will give us the next prime number every time we call it. Again, we’ll use our isPrime
function from before to check if a number is prime:
function* getNextPrime() {
let nextNumber = 2;
while (true) {
if (isPrime(nextNumber)) {
yield nextNumber;
}
nextNumber++;
}
}
If you’re used to JavaScript, some of this stuff will look a bit like voodoo, but it’s actually not too bad. We have that strange asterisk after the keyword function
, but all this does is to tell JavaScript that we’re defining a generator.
The other funky bit would be the yield
keyword. This is actually what a generator spits out when you call it. It’s roughly equivalent to return
, but it keeps the state of the function instead of rerunning everything whenever you call it. It “remembers” its place while running, so the next time you call it, it carries on where it left off.
This means that we can do this:
const nextPrime = getNextPrime();
And then call nextPrime
whenever we want to obtain — you guessed it — the next prime:
console.log(nextPrime.next().value); // 2
console.log(nextPrime.next().value); // 3
console.log(nextPrime.next().value); // 5
console.log(nextPrime.next().value); // 7
You can also just call nextPrime.next()
, which is useful in situations where your generator isn’t infinite, because it returns an object like this:
console.log(nextPrime.next());
// {value: 2, done: false}
Here, that done
key tells you whether or not the function has completed its task. In our case, our function will never finish, and could theoretically give us all prime numbers up to infinity (if we had that much computer memory, of course).
Cool, so Can I Use Generators and Iterators Now?
Although ECMAScript 2015 has been finalized and has been in the wild for some years, browser support for its features — particularly generators — is far from complete. If you really want to use these and other modern features, you can check out transpilers like Babel and Traceur, which will convert your ECMAScript 2015 code into its equivalent (where possible) ECMAScript 5 code.
There are also many online editors with support for ECMAScript 2015, or that specifically focus on it, particularly Facebook’s Regenerator and JS Bin. If you’re just looking to play around and get a feel for how JavaScript can now be written , those are worth a look.
Conclusions
IGenerators and iterators give us quite a lot of new flexibility in our approach to JavaScript problems. Iterators allow us a more Pythonic way of writing for
loops, which means our code will look cleaner and be easier to read.
Generator functions give us the ability to write functions that remember where they were when you last saw them, and can pick up where they left off. They can also be infinite in terms of how much they actually remember, which can come in really handy in certain situations.
Support for these generators and iterators is good. They’re supported in Node and all modern browsers, with the exception of Internet Explorer. If you need to support older browsers, your best bet is to use a transpiler such as Babel.