Beginners ES6 and Babel Tutorial

JavaScript (or ECMAScript) as a language is being updated faster than ever before. With these updates comes a whole slew of new language features and sugar that really take the pain out of some of the previously most complex or convoluted areas. Browsers and other JavaScript engines (node.js) haven’t quite caught up with this evolving spec, however there exists a way to use (most) of these features today, and that is Babel.js.

In this guide I’ll cover how to use Babel to develop code with ES2015 features today. I’ll also cover a good chunk of what those features are, and how they can be used to make your code more readable and succinct.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.

Code Examples

Even though I’ll explain how to setup your projects to use Babel and ES2015 today, the easiest way to experiment with the snippets and examples is actually by using the online REPL provided by Babel on the website. You’ll be able to execute the snippets as well as see the transpiled output right there live on the page in real time.

Getting Setup with Babel

Actually, the best source of information on how to do this is actually in the documentation on the babeljs website. There exists instructions for pretty much every scenario that you can imagine from raw CLI to node.js to Meteor. I recommend checking that page out for even more details, however I’ll go over a couple of common scenarios (at least for me).

The two places that I’ll want to use Babel is in my node.js applications, and in the browser. For node first we need to setup a project for use with node (or just install into a previous project) the core babel files and the ES2015 features preset. All of the features that babel will enable can be individually turned on and off, there also exists groups of presets to make that a little less tedious when you just want to blanket features like this.

npm init -y && npm install --save babel-core babel-preset-es2015

Babel will also look for a configuration file called .babelrc  in the path of your project. This will tell babel what options to use, including which presets to use, which is all we’ll need in the file for now.

{
    presets: ['es2015']
}

Now we can use it one of two ways, on the fly or pre-compiled. If you’re going to pre-compile your code, then you’ll likely want to use a build system like gulp. The instructions in the readme for the gulp-babel repo explain it very clearly, so if you are going that way, just read that.

What I found to be less clear is how to do on the fly compilation within node applications. It’s actually quite easy, there is a hook built in that will automatically compile on each require, simply call this hook at the top.

require('babel-core/register');

Now we can test that is working by creating a normal js file to require and put some ES2015 code inside of it. Don’t worry about what all this code does right now, I’ll go over the features of ES2015 in detail shortly.

function test1(foo, ...args) {
    let [bar, baz] = args;

    for(let arg of args) {
        let bar = 22;
        console.log(`arg: ${arg} bar: ${bar}`);
    }

    console.log(`done: ${bar} :: ${baz}`);
}

module.exports = test1;

Then save that as test1.js and we can create another js file onthefly.js to load it.

require('babel-core/register');

var test = require('./test1.js');

test('aa', 'b', 22, 43);
// arg: b bar: 22
// arg: 22 bar: 22
// arg: 43 bar: 22
// done: b :: 22

Compiling for the browser is a similar idea, you’re going to want to use a build system like gulp, or perhaps you already have one for your project. You can also do on the fly compilation in the browser. If you are starting a new project that doesn’t already have a build system that you need to integrate with, then I highly recommend using jspm.

JSPM (JavaScript Package Manager) makes it really easy to do both on the fly compilation for development, and bundling for production. It also has built in support for ES2015 Modules using SystemJS. Modules aren’t really necessary (yet) for node as it has built in CommonJS require modules, but for the browser we still have to use some sort of library (requirejs, systemjs, etc). SystemJS uses the new spec, and eventually it will be supported everywhere eliminating the need for these other libraries, and making your code automatically compatible. I really can’t recommend JSPM enough.

What’s new in ES2015?

Many of the new things available in ES2015 are things that can already be accomplished in ES5. What they do provide is a way to write cleaner more readable code. They also can clean up some of the more confusing areas and help to reduce boilerplate code cutting down on the size of your applications. A handful of the new features are actually not able to be created in ES5 (at least not in the strict one to one sense).

There is actually quite a large list of changes and features, too many to really cover in this guide. I’ll talk about several of the important ones, but you can find a complete list as well as a browser matrix of who has implemented them here on the ECMAScript 6 compatibility table.

Block Scope

Let’s take a look at block scope first. If you’re familiar with JavaScript already then you should know that variables are scoped to the function they are declared in (or the global context). blockscopeUsing the let keyword allows you to scope a variable to the block that it’s in, rather than just the function. The block that it’s in is defined by the curly brackets, so inside for loops or if statements, still inside functions, etc. This is really helpful for the classic for loop scope confusion problem.

var links = [];
for(var i=0; i<5; i++) {
    links.push({onclick: function() { console.log('link: ', i); }});
}

links[0].onclick(); // link: 5
links[1].onclick(); // link: 5

View code in REPL

In the past you would need to wrap the value of i inside a function closure in order to make sure that you used the variable at the state that it was in during the time of the loop. Now with ES2015 we can use the let keyword to create a block scoped variable, within a loop it will create a new instance for each iteration.

var links = [];
for(let i=0;i<5;i++) {
    links.push({onclick: function() { console.log('link: ', i); }});
}

links[0].onclick(); // link: 0
links[1].onclick(); // link: 1

View code in REPL

Also let works within multiple blocks of a function, each block will get its very own instance.

function test() {
    let foo = 1;

    if (foo === 1) {
        let foo = 22;
        console.log(foo); // 22
    }

    if (foo === 22) { // will not execute
        let foo = 33;
        console.log(foo);
    }

    console.log(foo); // 1
}
test();

View code in REPL

Another block scoped keyword is const. Constants are block scoped, and also they can only be defined once (within their scope). Unlike variables once you set the value of a const any further attempts to change it will result in an error.

const msg = 'hello world';

msg = 123; // will silently fail, msg not changed

var msg = 123; // Syntax error: msg already defined.

Function and Class declarations are also block scoped. I’ll go over classes in a bit, but be aware of this. Most likely you’re not going to run into trouble as you are likely to use something that you’ve declared within a certain scope inside that same scope, in fact it makes some interesting things possible.

function makeAnimalClass(legs) {
  let AnimalClass;
  
  if (legs > 2) {
    class Animal {
      constructor(name) {
        this.name = name;
        this.legType = 'multiped';
      }
    }
    
    AnimalClass = Animal;
  } else {
    class Animal {
      constructor(name) {
        this.name = name;
        this.legType = 'biped';
      }
    }
    
    AnimalClass = Animal;
  }
  
  return AnimalClass;
}

var Animal = makeAnimalClass(4);
var dog = new Animal('dog');
console.log(dog); // {"name":"dog","legType":"multiped"}

View code in REPL

I’m not really sure why you would need that code, but the important part there is that within an if statement block, the declaration is unique. In fact, the Animal class is not even available outside of the if or else block, that is why we had to bind it to the AnimalClass variable. Same thing with functions:

function foo(bar) {
  var fn;
  
  if (bar === 1) {
    function steam() {
      console.log('steam 1');
    }
    
    fn = steam;
  } else {
    function steam() {
      console.log('steam 2');
    }
    
    fn = steam;
  }
  
  fn();
}

foo(1);

View code in REPL

In ES5 the output to the console is steam 2  , in ES2015 the output is steam 1  which is what we actually intended.

Fat Arrow Functions

Another exciting new feature is fat arrow functions. Fat arrow functions are a new shorthand way of declaring functions that also share scope with their parent.

var addOne = a => a + 1;
addOne(1); // 2

var add = (a, b) => a + b;
add(1,2); // 3

var addLogged = (a, b) => { 
    let c = a + b; 
    console.log(a, '+', b, '=', c); 
    return c; 
};
addLogged(1, 2); // 1 + 2 = 3 (returns 3)

View code in REPL

Basically we can omit the keyword function, and then the function body follows the fat arrow (equals sign plus greater than). The function will automatically return the value of the expression on the right side, no need for the return keyword. If we only have one parameter on the left side, the parentheses can be omitted as well. If we have something more complicated than just returning a value in the function body, then we can still use the curly brackets to define the body. If we do, we need to use the return keyword as usual.

I also mentioned that the scope of the fat arrow function is shared with the parent of said function. This means that the keyword this refers to the parent this. supercoolThis is super cool because now inside callbacks we can access this without having to wrap the handler inside a closure or use function binding.

function Numberwang(wang) {
  this.multiplier = wang;
}

Numberwang.prototype.wang = function(numbers) {
  let result = Math.floor(numbers.reduce((i, j) => i + j * this.multiplier, 0));
  console.log(result);
  return result;
};

var wanger = new Numberwang(2);
wanger.wang([1, 2, 3]); // 12

View code in REPL

Default Parameters

In ES5 all of the parameters of a function are optional. Anything that isn’t passed in is simply undefined. If we want to have a default value for a parameter, in ES5 we’d need to do something like this:

function add(a, b) {
    b = (!b && b !== 0) ? 1 : b;
    return a + b;
}

Of course the more parameters you have, the more code you need to write to test if they have been provided and if not select a default value. Instead in ES2015 we can simply write them like this:

function add(a, b=1) {
    return a + b;
}

The default parameters need to come after any parameters without defaults because there is no way to call a method and skip a place in the parameters. Not only is this more convenient to write but it also helps improve code readability. Now we can glance at the method signature and make an educated guess that the b parameter should be a number, and we know if we don’t provide it, it’ll use 1. This code readability also helps IDEs provide you with helpful hints and code completions.awesome

It’s also interesting to note that the expressions used for default parameters are not evaluated until they are needed. They can also be variables, even other parameters.

function add(a = 3, b = (x => Math.floor(Math.random() * x + x))(a), c = a) {
  return a + b + c;
}

console.log(add()); // 3 + random + 3
console.log(add(7)); // 7 + random + 7
console.log(add(1, 3)); // 5
console.log(add(11, 3, 42)); // 56
console.log(add(undefined, 7)); // 13

View code in REPL

Rest Parameters

Another way that we can deal with function parameters is by using the arguments variable that is provided to every function. In ES5 we can accept a virtually unlimited range of parameters and process them dynamically. This is great except that we always get *all* of the parameters and that the arguments object is array-like, but not an actual array. In ES2015 we can use the rest parameter which is always an array and always only the arguments that weren’t provided ahead of it.

function add(a, b, ...more) {
    return a + b + more.reduce((i, j) => i + j, 0);
}

console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4)); // 10

View code in REPL

In order to achieve this same behavior in ES5 it is slightly more cumbersome.

function add(a, b) {
    var more = Array.prototype.slice.call(arguments, 2);
    return a + b + more.reduce(function(i, j) { return i + j; }, 0);
}

Not too difficult however we have to dive into the implementation of the method in order to understand what’s happening whereas with the Rest parameter we know that there will be an additional array of values used from the arguments passed in just from the signature. We also don’t have to worry about index counting or building an array out of the rest.

Spread operator

Sort of like the opposite of rest parameters (but not quite) is another awesome feature, the spread operator. The spread operator allows you to spread out the values of an array as if they were arguments. This makes calling functions with dynamic arguments much nicer. Now instead of calling apply we can simply call the function using the spread operator.

var vals = [1, 2];

function add(a, b) {
    return a + b;
}

console.log(add(...vals)); // 3

View code in REPL

The spread operator can also be used when constructing arrays.

var vals = [1, 2];
var others = [3, ...vals, 4]; // [3, 1, 2, 4]

We can put the spread anywhere in the array, much easier and less code than having to use slice and splice and concat like before. It also enables us to create factory functions more easily, as you can’t normally call a constructor using apply or call. With the spread operator, it is natural and easy.

function Foo(a, b) {
    this.a = a;
    this.b = b;
}

function fooFactory() {
    var params = [1, 2];
    return new Foo(...params);
}

var foo = fooFactory();

Destructuring

We’ve actually already encountered some examples of destructuring, default parameters and spread operator are made possible with destructuring. Destructuring is the ability to extract values from objects or arrays into variables. Let’s think about the classic function parameter options that is a configuration object for the method. In ES5 we would need to reference each key in the object or if we didn’t want to have to write out the options prefix or only needed a small subset of the values, create variable references.

function foo(options) {
  var count = options.count;
  var msg = options.msg;
  
  for(var i=0;i<count;i++) {
    console.log((i + 1) + '. ' + msg);
  }
}

foo({count: 3, msg: 'something', junk: 88});
// 1. something
// 2. something
// 3. something

View code in REPL

Instead in ES2015 we can use destructuring to make this code a lot cleaner.

function foo(options) {
  let {count, msg} = options;
  
  for(let i=0;i<count;i++) {
    console.log(`${i + 1}. ${msg}`);
  }
}

foo({count: 3, msg: 'something', junk: 88});

View code in REPL

The code pulls out each property by name, and assigns it to an individual variable for use within the scope. You can also change the name of the local variable instead of each one needing to be named exactly after the property.

function foo(options) {
    let {count: max, msg} = options;
 
    for(let i=0;i<max;i++) {
        console.log(msg);
    }
}

Destructuring can be used on arrays as well, instead of the key names of an object, the order of the item is used.

let arr = [1, 2, 3];

let [x, y, z] = arr;

console.log(x); // 1
console.log(y); // 2
console.log(z); // 3

View code in REPL

I think that’s really a lot nicer than lining up with each index of an array, don’t you? You can also use destructuring inside for loops and you can use default values as well.

let people = [];

function addPerson({first = 'John', last = 'Doe', middle = 'wait for it...'} = {}) {
  people.push({first, last, middle});
}

function display() {
  for(let {first, last, middle} of people) {
   console.log(`${first} ${middle} ${last} is awesome.`); 
  }
}

addPerson({first: 'Ben', last: 'Sparks'});
addPerson({first: 'Jane', middle: 'Q', last: 'Public'});
addPerson({middle: 'Q'});
addPerson();

display();

// Ben wait for it... Sparks is awesome.
// Jane Q Public is awesome.
// John Q Doe is awesome.
// John wait for it... Doe is awesome.

View code in REPL

Combined with the default values, you can really read right in the method signature what exactly the object that is being passed in is expected to look like. It’s also more succinct code to work with exactly what you need without having to endure all of the extra work to sort the variables out.

Template String Literals

Another thing that often produces messy code in ES5 is string concatenation. Either by using the + sign, or by using an array with join it leads to code that is difficult to format, and hard to read. Now in ES2015 we can use the back tick character (`) to build multi-line strings directly into the code. Also within these template strings we can use ${} syntax to reference variables and expressions.

let nouns = ['ape', 'sandwich', 'pencil'];
let adjectives = ['purple', 'slimey', 'strange', 'huge'];
let adverbs = ['slowly'];

function getRandom(bank = ['word']) {
  return bank[Math.floor(Math.random() * bank.length)];
}

let madlib = `
  Once upon a ${nouns[0]} there was a ${adjectives[0]} ${nouns[1]}.
  Its ${nouns[2]} was ${adverbs[0]} turning ${adjectives[1]}.
  1 + 2 = ${1 + 2}. How ${getRandom(adjectives)}}...
`;

console.log(madlib);


  // Once upon a ape there was a purple sandwich.
  // Its pencil was slowly turning slimey.
  // 1 + 2 = 3. How strange...

View code in REPL

Any JavaScript expression inside the ${} delimeter will be evaluated. The templates are evaluated immediately and have access to the scope in which they are called.

Classes

The addition of classes to JavaScript might seem like a completely new concept but they aren’t really. Just like many of the other features that are new to ES2015 classes merely provide clean code and syntactic sugar on top of concepts that already exist.

We can define a class using the new class keyword. In the class definition we can specifically define a constructor method as well as other instance methods and getters and setters for properties.

class Animal {
    constructor(name, voice) {
        this.name = name;
        this.voice = voice;

        this._eyes = 2;
    }

    get eyes() {
        return this._eyes;
    }

    speak() {
        console.log(`The ${this.name} says ${this.voice}.`);
    }
}

var foo = new Animal('dog', 'woof');
foo.speak(); // The dog says woof.
console.log(`${foo.name} has ${foo.eyes} eyes.`); // dog has 2 eyes

View code in REPL

Subclassing is also a lot easier to do in ES2015 than it was in ES5. Now we can create a subclass using the extends keyword. We also have access to parent methods using the super method.

class Dog extends Animal {
    constructor() {
         super('dog', 'woof');
    }
}

class Goldfish extends Animal {
    constructor() {
         super('goldfish', 'glub');
    }

    speak() {
         console.log(`${this.name} does not speak.`);
    }
}

var fido = new Dog();
fido.speak(); // The dog says woof.
var goldie = new Goldfish();
goldie.speak(); // goldfish does not speak.

View code in REPL

You can also attach methods and properties to the main class so that you can access them without an instance, these are said to be static properties, and you use the static keyword.

class Foo {
   constructor(a) {
      this.a = a * Foo.modifier;
   }

   static get modifier() { return 2; }

   static bar(instance) { if (instance instanceof Foo) { console.log('my baby!'); } }
}

let foo = new Foo(7);

console.log(foo.a); // 14

Foo.bar(foo); // my baby!

View code in REPL

Maps and Sets

Now we have something that other languages have had for quite some time, Maps and Sets.

In ES5 generally we use an Object as a Map of key/value pairs, but we have to test that some of the keys are not ones generated as part of the prototype chain. Instead we can now use Map, which handles all that properly, has a readable API and also allows us to use arbitrary values as keys, even other objects. You can also initialize a Map with an array of key, value array pairs.

var gameGenres = new Map([
    ['Zelda', 'ActionRpg'],
    ['Metroid', 'Platformer']
]);

gameGenres.set('Skyrim', '<a class="wpil_keyword_link" href="https://gamedevacademy.org/best-rpg-tutorials/" target="_blank" rel="noopener" title="Rpg" data-wpil-keyword-link="linked">Rpg</a>');

gameGenres.get('Zelda'); // ActionRpg

gameGenres.has('Zelda'); // true

gameGenres.delete('Metroid'); // true

gameGenres.has('Metroid'); // false

var Castlevania= {name: 'Castlevania', hero: 'Simon'};

gameGenres.set(Castlevania, 'Metroidvania');

Iterating over a Map is also a lot easier than doing so with an Object. In ES5 we would loop over the keys and check the values like this:

var obj = {
    'a': 1,
    'b': 2,
    'foo': 7
};
var values = [];

for (var key of obj) {
    values.push(obj[key]);
    if (obj[key] === 7) {
        console.log(key + ' has a lucky value!');
    }
}

Now we can easily access all of the keys, all of the values, or both.

var foo = new Map([
    ['a', 1],
    ['b', 2],
    ['foo', 7]
]);

var values = [...foo.values()]; // [1, 2, 7]
var keys = [...foo.keys()]; // ['a', 'b', 'foo']
var entries = [...foo.entries()]; // [['a', 1], ['b', 2], ['foo', 7]]

for (let [key, value] of foo) {
    if (value === 7) {
        console.log(`${key} has a lucky value ${value}!`);
    }
}

View code in REPL

Note that values, keys, and entries are actually not arrays, they are iterable objects (which we’ll get to in a bit) and that is why I am converting them to arrays using the spread operator.

Just like Map is a more refined version of using an Object, a Set is a more advanced version of an array. A set is basically an array that can only contain unique values. It has a very similar API to Map, with add, has, delete, and clear methods.

let nums = new Set([1, 2, 3 , 4, 5, 6, 6, 6]);
console.log([...nums]); // [1,2,3,4,5,6]
nums.add(1);
nums.add(7);
console.log([...nums]); // [1,2,3,4,5,6,7]
console.log(nums.has(3)); // true
nums.delete(2);
console.log(nums.has(2)); // false
nums.clear();
console.log([...nums]); // []
nums.add(1).add(2).add(3);
for(let x of nums) {
  console.log(`value: ${x}`);
}
// value: 1
// value: 2
// value: 3

View code in REPL

When you make a call to add it checks if the value already exists (using equality testing), if so it simply does nothing. Just like Map, a Set is not an array and so can’t use methods like map or filter like arrays can. In order to do that, you first need to convert it to an array, and then convert the result back to a set.

Promises

Promises have been around for a while in the form of various libraries and plugins. Now they are a built in part of the language. If you’re not familiar with promises, they are a way to do asynchronous programming. We can execute a method and wait for the result to be processed without locking up the rest of the application with the execution.

function asyncFn(foo) {
    return new Promise((resolve, reject) => {
        if (foo === 7) {
            return resolve('lucky winner');
        }

        return reject('oh too bad!');
    });
}

asyncFn(7).then(success => console.log(success), failure => console.log(failure));

View code in REPL

The execution of a async method can result in either success or failure (or have exception errors). One of the more obvious examples is an ajax call to the web. We need to wait for the server to respond and we don’t want to have to sit there and do nothing while we wait, we want to be able to continue to do other things in the application. When the server finally does respond it will either respond with a success code like 200 or some sort of error like 404. We can set callback methods to handle either scenario as we see fit.

Most likely you have already encountered promises before, jQuery has them as deferreds and Angular uses the q library. The exciting news with ES2015 is that they are now a part of JavaScript. I encourage you to check out the full API at the MDN docs page.

Symbols

Symbols are a new primitive type in ES2015. They are values that are guaranteed to be unique. You can create a symbol using the factory method Symbol()  optionally you can pass in a string argument to describe or label the symbol Symbol(‘foo’)  however that does not affect the uniqueness.

var foo = Symbol('foo');
var bar = Symbol('foo');
foo === bar // false

So what are these new symbols good for? Well mainly they are something we can use in place of strings to identify everything. They can be used as keys of an object as well. Because they are unique and they are not strings, you can use them to create “private” variables and methods. One very common method of identifying private members of an object is by simply using a naming convention, like putting an underscore at the beginning of the variable name. So one might implement a class like this in ES5.

function Foo() {
    this.publicMethod = function() { console.log('public'); };
    this._privateMethod = function() { console.log('private'); };
}

In JavaScript however there isn’t really any such thing as private scope, only scope that you don’t have access to because of a closure above. Of course, you can access the value of an object by key using bracket notation, so you can simply type foo[‘_privateMethod’]();  to invoke the method. With a naming convention, the best you can do is say “methods prefixed with an underscore are meant to be private, please don’t invoke them or override them”. This might work fine when you are talking to another developer or describing your library to them in your documentation, however you can’t expect that all third party libraries will know of this convention or do their due diligence to test your code before they act. Let’s say for example we have a class with a private method _log and another library that wants to add its own private method _log to every object. In order to avoid issues the 3rd party library needs to check if the _log method already exists, and then if it does, determine a way around it, either by calling both and injecting their functionality into it, or dynamically renaming their own method and keeping track of it somehow. Implementing workarounds for a problem that might not even exist is a lot of extra work, and the workaround solutions aren’t guaranteed to be perfect. Also you can’t use a string inside a closure because the value of a string is always equal to the equivalent string (JavaScript maps all strings to a table inside the engine to save memory).

Using symbols however in a closure can solve this problem.

(function() {
    var privateLog = Symbol();

    function Foo() {
        this[privateLog] = (msg) => console.log('private', msg);
        this.publicMethod = () => {
            // do something
            this[privateLog]('doing something');
        };
    }

    window.Foo = Foo;
})();

function mixinLogger(target) {
    target[mixinLogger.symbol] = (msg) => console.log('mixed log: ', msg);
}
mixinLogger.symbol = Symbol('mixinLogger log method');
mixinLogger.log = (target, msg) => target[mixinLogger.symbol](msg);

var foo = new Foo();
mixinLogger(foo);
// shortcut mixin logger for foo
foo.mlog = function(msg) { mixinLogger.log(this, msg); };

View code in REPL

Of course, even this way the methods are not truly private. We’re still publicly exposing everything that is attached to “this”. Doing it this way makes it a lot harder to accidentally modify or collide with other properties. Even enumerating through the keys of an object is more deliberate, Object.keys()  only displays the enumerable string keys of an object. In order to find the keys there is a new method on Object, getOwnPropertySymbols that we can use. There is also another method getOwnPropertyNames that will return all of the string keys for an object (enumerable or not).

Object.defineProperty(foo, 'foo', {enumerable: false, value: 42});

Object.keys(foo); // ["publicMethod", "mlog"]
Object.getOwnPropertyNames(foo); // ["publicMethod", "mlog", "foo"]
Object.getOwnPropertySymbols(foo); // [Symbol(), Symbol(mixinLogger log method)]

Iterables and Iterators

These are something that is sort of new to JavaScript unlike many of the other changes. Something is iterable when you can loop over its values. An iterator is the object that keeps track of the current place while looping.

The first iterable that likely comes to mind is the Array. Maps, Sets, and Strings are also iterable. Iterables work by following a specific interface. They implement a method that returns an iterator object. This method is identified by the Symbol.iterator built in Symbol (we learned from Symbols that we can avoid using string names to identify things). The iterator object is one that has a next() method which will return the “next” item in the collection and keep track of where it is in the collection. The returned value from next is an object that has the following signature {value: <any value>, done: <boolean>} . By following this pattern the built in iterable objects are able to be looped through using for loops, or you could even do something custom by just calling next until you get true for done.

Because even the native objects are following this interface and pattern, it is possible to make any object become an iterable collection that we can loop on. In fact, doing this might make all of this a bit clearer.

class Counter {
  constructor(start = 0, end = 10) {
    this.start = start;
    this.end = end;
  }
  
  [Symbol.iterator]() {
    let current = this.start;
    let max = this.end;
    
    return {
      next() {
        let result = {value: current, done: current > max};
        current++;
        return result;
      }
    };
  }
}

let counter = new Counter(1);
for (let i of counter) {
  console.log(i);
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
console.log([...counter]); // [1,2,3,4,5,6,7,8,9,10]

View code in REPL

Even though the Counter class is not an array or set, we can do all of the fun stuff that we can as if it were. This opens the doors to all kinds of cool things like linked lists and file readers. An iterator can also have a return and throw method optionally. Return is used when a loop is broken prematurely using break, continue, return, etc and throw is used along with generators which I’ll talk about next.

Generators

Generators are new and are functions that can be paused and resumed. They also follow the iterable pattern. You declare a function to be a generator using the * next to the normal method declaration. Within the generator method, you use the special keyword yield to pause the execution in place, and the next time that it is entered it will pickup at that point. The generator method isn’t executed directly however, it returns a generator object, which follows the iterator pattern.

function* generator() {
  yield 'foo';
  yield 'bar';
}

let gen = generator();

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

View code in REPL

You can nest generators using the yield* keyword.

function* gen() {
  yield 1;
  yield* gentoo();
  yield 4;
}

function* gentoo() {
  yield 2;
  yield 3;
}

console.log([...gen()]); // [1, 2, 3, 4]

View code in REPL

Generators can also be consumers of data that is passed into the next method. When you do so, it becomes the value of the yield keyword. Because calling yield causes the function to pause, the very first call to next will not reflect the value passed in. It’s a little strange, but you can think of the first call to next as initializing the consumer generator.

function* gen() {
  let x = yield 'init';
  if (x === 2) {
    yield 7;
  } else {
    yield 22;
  }
}

let foo = gen();
console.log(foo.next()); // {"value": "init","done":false}
console.log(foo.next(2)); // {"value":7,"done":false}
console.log(foo.next()); // {"done":true}

console.log([...gen()]); // [null, 22]

View code in REPL

Generators and their use and application is a very deep and complicated subject. I’ve really only scratched the surface with this one, and I hope that I’ve peaked your interest enough to further research how generators can help you.

Armed and Dangerous

This guide is not an exhaustive list of every single new change to EcmaScript / JavaScript with ES2015. It is however meant to highlight some of the larger areas that I think you’ll want to know about first – especially as you pursue developer jobs or similar. For a really in depth look at absolutely everything, I recommend checking out this ebook, Exploring ES6 as it is a most excellent resource, and my go-to guide.

Now go and write some next gen JavaScript!

bruce_keys

If you have any questions or want to know more about something specific, please post it in the comments. I’ll be happy to answer it there and/or update this guide as needed.