Zombies are fun. ES2015 is fun. You know what’s even more fun? ES2015 and zombies together! In this post, I’ll show you how I used ES2015 generators to make an infinite zombie horde.
[more]
Getting Started
This sample should work fine in NodeJS 8+. Go grab it if you don’t have it already!
Let’s make a folder for our zombie project, and then let’s go ahead and create a few files:
main.js
Zombie.js
ZombieFactory.js
main.js
will be our application entry point. We’ll come back to that later when it’s time to put all the pieces together.
Our Zombie Class
Let’s start by creating our Zombie class. We’ll use the ES2015 class
feature for this:
//Zombie.js
module.exports = class Zombie {
constructor(name, condition) {
this.name = name;
this.condition = condition;
}
sayHi() {
return 'BRAAAAAAAINS';
}
};
Even for this simple example, the class
keyword makes things far easier to grok than the old ES5 way.
Notice that module.exports
bit at the top? That’s how Node currently handles defining modules. I wish we could use true ES2015 modules, but alas, we can’t (yet).
The Zombie Factory
It’s time to crank out some zombies. Let’s start by defining our factory’s basic structure!
//ZombieFactory.js
const Zombie = require('./Zombie');
module.exports = class ZombieFactory {
};
Just a simple class so far. Let’s go ahead and define a function to return an infinite horde using ES2015’s generator feature!
const Zombie = require('./Zombie');
module.exports = class ZombieFactory {
*getHorde() {
}
};
Notice that *
(asterisk) before the function name? That denotes that the method is a generator.
"What is a generator??", you ask? If you are coming from a C# background, think of it like a method that returns an IEnumerable<T> using
yield return
.
Implementing the Generator
Let’s define a couple of arrays to hold possible values. Each zombie will get a random name from this list…
const names = [
'Cannibal',
'Shambler',
'Melter',
'Hunter',
'Pincher',
'Stinger',
'Crier',
'Stinker',
'Creeper',
'Crawler'
];
And they will get a random condition from this list of possible conditions:
const conditions = [
'fresh',
'slightly-decayed',
'decayed',
'horribly-decayed'
];
If we wanted to make and return a single random zombie, we’d do it like this:
const name = names[Math.floor(Math.random()*names.length)];
const condition = conditions[Math.floor(Math.random()*conditions.length)];
return new Zombie(name, condition);
Note: The
Math.floor
bits just select a random value from each array.
But we don’t want to return just a single zombie, we want to return an infinite horde of zombies! So instead of return
ing a single zombie, we yield return
a zombie inside of an infite loop:
while (true) {
const name = names[Math.floor(Math.random()*names.length)];
const condition = conditions[Math.floor(Math.random()*conditions.length)];
yield new Zombie(name, condition);
}
Our final ZombieFactory.js file should look like this:
const Zombie = require('./Zombie');
module.exports = class ZombieFactory {
*getHorde() {
const names = [
'Cannibal',
'Shambler',
'Melter',
'Hunter',
'Pincher',
'Stinger',
'Crier',
'Stinker',
'Creeper',
'Crawler'
];
const conditions = [
'fresh',
'slightly-decayed',
'decayed',
'horribly-decayed'
];
while (true) {
const name = names[Math.floor(Math.random()*names.length)];
const condition = conditions[Math.floor(Math.random()*conditions.length)];
yield new Zombie(name, condition);
}
}
};
And that is how you make a generator that returns a never-ending stream of zombies! Let’s see how to put this stream to good use…
Using a Generator
When we call a JavaScript generator function, we get back an iterator. Over in main.js, let’s import our factory (note that we do not have to import the Zombie class directly), and let’s grab our iterator.
//main.js
const ZombieFactory = require('./ZombieFactory');
const factory = new ZombieFactory();
//**This is our iterator!**
const horde = factory.getHorde();
We can then grab a zombie from the iterator by calling the next
function:
const zombie = horde.next().value;
console.log(zombie.sayHi());
But a horde does not have a single zombie. It has a lot of zombies. Let’s grab a ton of zombies!
for (let i = 0; i < 10000; i++) {
const zombie = horde.next().value;
console.log(zombie.sayHi());
}
10,000 isn’t enough? FINE!
for (let i = 0; i < 10000000; i++) {
const zombie = horde.next().value;
console.log(zombie.sayHi());
}
Capturing a Herd of Zombies
So far, we’re just iterating through each zombie, then discarding it. We’re not really building up a herd, we’re just moving through one!
We can capture a herd from our horde easily enough:
const herd = [];
const desiredSize = 10000;
while (herd.length < 10000) {
herd.push(horde.next().value);
}
Do keep in mind that when you do this, you’re really not getting any benefit from using a generator. You could have just as easily created a function that initialized each member of an array of a fixed size with a zombie.
Final Thoughts
Generators are great when you want to work with things as a stream. Think paged result sets. Think infinite series. These are things you can model using vanilla ES5, but they can be modeled more effectively with ES2015 generators.
And sometimes, like in this case, they’re just more fun to use. ?