ES6 in io.js

By  on  

io.js - the famous Node.js fork recently put out their initial release touting the slogan "Bringing ES6 to the Node Community!". io.js got these features ahead of Node.js by aggressively following the latest versions of the V8 JavaScript engine. As an outsider looking in, I took a few hours to poke around, and shall report my findings here.

Installation

Binary distributions of io.js are available from their front page, and you can download a binary for Linux, Windows, Mac, or build it from source. However, the binary installers will overwrite the node and npm executables on your system if you have Node.js installed. Therefore, I recommend using nvm to install io.js in a conflict-free way. Installing nvm is quite easy if you haven't done it before. If and when you have nvm, simply do

$ nvm install io.js
######################################################################## 100.0%
WARNING: checksums are currently disabled for io.js
Now using io.js v1.0.3

Check that it worked:

$ iojs
>

Voilà! Note that node is aliased to iojs, and npm is still called npm.

ES6 Features Overview

Although some folks have already been using ES6 for a while via transpilers, when I work with transpiled code, it feels like I am having to simultaneously debug two versions of the code - debugging is hard enough with just one version. For this reason, having native support makes it much more appealing for me.

The io.js ES6 page gives information about the changes they've made to the ES6 support in the engine. They have done away with the --harmony flag - which in Node 0.11+ you had to include if you wanted to use any ES6 features at all. In io.js, you get them right out of the box! The current list of ES6 features enabled by default are:

  • let statement
  • const statement
  • Map and Set
  • WeakMap and WeakSet
  • Generators
  • Binary and Octal literals
  • Promises
  • Some additional string methods
  • Symbols
  • Template strings

They also added the --es_staging flag which would allow you to gain access to features that are done but haven't been well tested yet. For features that are in progress of being implemented, you'd have to gain access to each feature individually by using the harmony flag corresponding to it. You can get the list of harmony feature flags via:

$ iojs --v8-options|grep "harmony"
  --es_staging (enable all completed harmony features)
  --harmony (enable all completed harmony features)
  --harmony_shipping (enable all shipped harmony fetaures)
  --harmony_modules (enable "harmony modules (implies block scoping)" (in progress))
  --harmony_arrays (enable "harmony array methods" (in progress))
  --harmony_array_includes (enable "harmony Array.prototype.includes" (in progress))
  --harmony_regexps (enable "harmony regular expression extensions" (in progress))
  --harmony_arrow_functions (enable "harmony arrow functions" (in progress))
  --harmony_proxies (enable "harmony proxies" (in progress))
  --harmony_sloppy (enable "harmony features in sloppy mode" (in progress))
  --harmony_unicode (enable "harmony unicode escapes" (in progress))
  --harmony_tostring (enable "harmony toString")
  --harmony_numeric_literals (enable "harmony numeric literals")
  --harmony_strings (enable "harmony string methods")
  --harmony_scoping (enable "harmony block scoping")
  --harmony_classes (enable "harmony classes (implies block scoping & object literal extension)")
  --harmony_object_literals (enable "harmony object literal extensions")
  --harmony_templates (enable "harmony template literals")

Now, let's drill down to the individual features.

let and const

The let and const statements are only available in strict mode. So put "use strict" at the top of each JS file where you wish to use them.

The let statement is a replacement for the var statement which has lexical scoping. What this means is that while a variable defined with var is visible to the function within which it is declared, let is visible only to the code block within which it is declared. In JavaScript, a code block is a compound statement enclosed in { and } that contains zero or more statements. You commonly use code blocks within if-statements, for loops, while loops, and as the body of a function definition. But, it's also possible to write a standalone code block.

Here is an example of let:

"use strict"
if (player.partner){
  let partner = player.partner
  // do stuff with partner here
}
console.log(parter) // this throws partner is not defined

Here is let in a for loop:

"use strict"
for (let i = 0; i < 10; i++){
  console.log(i)
}
console.log(i) // this throws i is not defined

const is like let except that once declared, the variable cannot be reassigned to another value.

"use strict"
const ITERATIONS_TO_RUN = 10
ITERATIONS_TO_RUN = 12 // throws TypeError: Assignment to constant variable.

Map and Set

ES6 has introduced the Map and Set data structures for your convinience. Now you might be wondering, why do we even need a map? What's wrong with using object literals as maps? Well, it's been argued that an object is not a hash (or er map). The short version is that an object inherits all of Object.prototype's properties, which in most cases is unwanted if you want to use it as a map.

Now, here's an example of using Map:

> var m = new Map
undefined
> m.set('name', 'Bobby')
{}
> m.get('name')
Bobby
> m.size
1
> m.set('age', 5)
{}
> m.has('age')
true
> m.has('foobar')
false
> m.forEach(function(value, key){ console.log(key + ' maps to ' + value) })
name maps to Bobby
age maps to 5
> m.get('hasOwnProperty') // avoids the `hasOwnProperty` trap
undefined
> m.clear()
undefined
> m.size
0

And here is Set in action:

> var s = new Set
undefined
> s.add(1)
{}
> s.size
1
> s.add(2)
{}
> s.size
2
> s.add(1) // adding a duplicate here
{}
> s.size   // no change in size
2
> s.has(1)
true
> s.has(2)
true
> s.has(3)
false
> s.forEach(function(n){ console.log('Set has ' + n) })
Set has 1
Set has 2

WeakMap and WeakSet

WeakMap and WeakSet are new data types that mirror Map and Set, but unlike Map and Set - which can be implemented as polyfills - these can only be implemented natively. The word "weak" refers to weak references. A weak reference is an object reference that is ignored by the garbage collector. If there exists only weak references - no more strong references - pointing to the object in question, then that object can be destroyed and its memory relinquished.

Let's talk about WeakSet first - because it is easier to explain. A WeakSet's API is a subset of Set's. However, you cannot store primitive values in it:

> var ws = new WeakSet
undefined
> ws.add(1)
TypeError: Invalid value used in weak set

This makes sense because primitive values are stored by value, not by reference, and it would make no sense to even speak of weak references. So, you'll need to put objects in it instead:

> var bob = {name: 'Bob'}
undefined
> var jen = {name: 'Jen'}
undefined
> ws.add(bob)
{}
> ws.add(jen)
{}
> ws.has(bob)
true
> ws.has(jen)
true
> var jim = {name: 'Jim'}
undefined
> ws.has(jim)
false
> ws.delete(jen)
true
> ws.has(jen)
false

WeakSet has no size property, or a way of iterating its members

> ws.size
undefined
> ws.forEach(function(item){ console.log('WeakSet has ' + item)})
TypeError: undefined is not a function
> ws.forEach
undefined

This is precisely because the references are weak, and as such, the objects could be destroyed without notice, at which point it would be impossible to access them anymore. One possible use of WeakSet is to store a set of related DOM elements without worry of memory leaks when the elements are removed from the document.

A WeakMap is like Map except that all of its keys are weak references. They also must not be primitive values.

var wm = new WeakMap
> var person = {name: 'Bob'}
undefined
> var creditCard = {type: 'AMEX', number: 123456789}
undefined
> wm.set(person, creditCard)
{}
> wm.get(person)
{ type: 'AMEX', number: 123456789 }

As with Set, there is no way to get the size of the WeakMap or iterate over it's keys or values:

> wm.size
undefined
> wm.forEach
undefined

When the application ceases to hold a strong reference to person, its entry in wm could be destroyed, and creditCard could in turned be destroyed as well. Read more about WeakMap and WeakSet.

for-of

In addition to the classic for-in statement, ES6 has added the for-of statement which allows you to succiently iterate the values of arrays, iterables, and generators. The latter two to be discussed below.

Here is for-of iterating over an array:

> var arr = [1, 2, 3]
undefined
> for (var n of arr) console.log(n)
1
2
3

Iterables and Iterators

So, you can also use the for-of statement to iterate over iterables.

But what is an iterable?

An iterable is an object that has an associated method which initializes and returns an iterator. The way you associate this method with an object is:

var myObj = {}
myObj[Symbol.iterator] = function(){  // I'll cover symbols later
  return new MyIterator
} 

But what is an iterator?

An iterator is an object that adheres to the iterator protocol - which requires simply one method:

  • next() - which advances to the next item in the sequence each time it is called and returns an object that contains two properties
  • done - a boolean which is true if and only if the sequence has already ended
  • value - the current value in the sequence

As an example, below is how I've managed to make a simple custom link list implementation iterable:

function LLNode(value){
  this.value = value
  this.next = null
}
LLNode.prototype[Symbol.iterator] = function(){
  var iterator = {
    next: next
  }
  var current = this
  function next(){
    if (current){
      var value = current.value
      var done = current == null
      current = current.next
      return {
        done: done,
        value: value
      }
    }else{
      return {
        done: true
      }
    }
  }
  return iterator
}

var one = new LLNode(1)
var two = new LLNode(2)
var three = new LLNode(3)
one.next = two
two.next = three

for (var i of one){
  console.log(i)
}

The output of this program is

1
2
3

Generators

Generators allow you to write an iterable in a succient and easily understandable manner. It also allows you to represent infinite sequences.

Here is how I could write a generator that iterates all integers starting from 0:

function *naturalNumbers(){
  var n = 0
  while (true){
    yield n++
  }
}

Note the function * syntax and the yield statement - these indicate that this is a generator function rather than a normal function. When you call a generator function, you get back a generator, which implements the iterator protocol:

> var gen = naturalNumbers()
{}
> gen.next()
{ value: 0, done: false }
> gen.next()
{ value: 1, done: false }

It's also an iterable! You can verify this: if you call its iterator method, you get back the generator itself:

> gen[Symbol.iterator]() === gen
true

But the more succient way to iterate over an iterable, of course, is via the for-of statement:

for (var n of naturalNumbers()){
  console.log(n)
}

Oops! Infinite loop (facepalm).

Generators are also cool because it is one solution (among several) to the callback hell problem. Notably, co and koa are frameworks which make heavy use of generators, and they both work in io.js out of the box. Read more for more in-depth treatments of generators.

Binary and Octal Numbers

Binary numbers are prefixed with 0b, and octal numbers are prefixed with 0O - that is, "zero" "O".

console.log(0b100)
console.log(0O100)

The above program outputs:

4
64

Promises

The development of promises was very much a grassroots effort, starting out as libraries or components within various frameworks. Today, there are estabilished libraries like RSVP, Q, and Bluebird. Most of the major frameworks have promises built-in. There is a standard for promises called Promises A+ which most of the major implementations adhere to. To top it off, promises have been brought into the runtime itself! The story behind promises is quite inspiring.

Below is an example of how to turn a callback-based http client library into a function that returns a promise:

var request = require('superagent')

fetch('http://iojs.org')
  .then(function(reply){
    console.log('Returned ' + reply.text.length + ' bytes.')
  })

function fetch(url){
  return new Promise(function(resolve, reject){
    request(url).end(function(err, reply){
      if (err){
        reject(err)
      }else{
        resolve(reply)
      }
    })
  })
}

Promises can also be used effectively with generators - which is the strategy employed by co. Read this tutorial for a more in-depth explanation of promises.

New String Methods

Some new methods have been added to the native String object.

  • String.fromCodePoint(number) and .codePointAt(idx) are like String.fromCharCode and .charCodeAt(idx) except that they support unicode and therefore high code points translate into multi-byte characters

    > s = String.fromCodePoint(194564)
    '你'
    > s.codePointAt(0)
    194564
    
  • startsWith(s) and endsWith(s)

    > 'Hello, world!'.startsWith('Hello')
    true
    > 'Hello, world!'.endsWith('!')
    true
    
  • repeat(n)

    > 'foobar'.repeat(5)
    'foobarfoobarfoobarfoobarfoobar'
    
  • normalize() - returns the unicode normalization form of the string. To actually understand what that means, read about unicode equivalence.

Symbols

The name symbol could be confusing because these symbols are not like the ones in Ruby or Smalltalk. Symbols in ES6 are used as hidden object properties. If you are a Pythonista: think double underscore magic methods.

var secretMethod = Symbol('secret')
var obj = {}
obj[secretMethod] = function(){
  return 'foobar'
}
obj[secretMethod]() // returns `foobar`

Now, secretMethod won't show up within a for-in loop through the object's properties. In fact, no string property corresponds to the symbol referenced by secretMethod and there is no way to access the method without having a reference to the symbol. There are global "well-known" symbols in the system such as Symbol.iterator - which we've seen used to associate an object with its iterator. By all means, read more about symbols.

Template Strings and Multi-line Strings

Template strings are borrowed from Ruby and Perl's string interpolation. It saves developers from having to awkwardly add together bits of strings - which often results in lots of quotes.

> var name = 'Bobby'
undefined
> `Hello, ${name}!`
'Hello, Bobby!'

Note that template strings are enclosed by upticks "`" rather than single or double quotes - you'll have to reach up with your left pinky. What's exciting to me is that you can now write multi-line strings:

var age = 5
var sql = `
select
  name
from
  people
where
  age > ${age};
`

Template strings have one more feature - to allow a custom function to evaluate the template in question. This is useful for situations which require specific parameter escaping - such as when sanitizing SQL parameters to prevent SQL injection attacks.

var age = 5
var sql = sqlSanitize`
select
  name
from
  people
where
  age > ${age};
`

You can read more for in-depth treatments of template strings.

Notable Features Behind Flags

Some of the notable features still marked as in progress in io.js - version 1.0.3 at the time of this writing - are:

Overall Impression

I feel optimistic about the state of ES6 features on io.js. I like that all of these features work out of the box without any special flags. Mentally, this designation makes these features legit. For the most part, when these features are used the wrong way, the thrown error messages are helpful in guiding users. The features I am most excited about are generators and template strings. If I were starting a new hobby project today I would definitely give io.js a try, have a play, go wild, and try out these features in the wild.

Toby Ho

About Toby Ho

Toby is a veteran JavaScript developer. He enjoys helping others learn via blogging, organizing meetups, giving workshops and teaching privately. He created the Testem interactive test runner - which is his proudest open source work to date.

Recent Features

  • By
    5 Awesome New Mozilla Technologies You&#8217;ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

  • By
    JavaScript Promise API

    While synchronous code is easier to follow and debug, async is generally better for performance and flexibility. Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready?  Promises are becoming a big part of the JavaScript world...

Incredible Demos

Discussion

  1. MaxArt

    Very interesting stuff, it’s nice to see some ES6 recaps sometimes.

    Just a couple of notes: I actually managed to install io.js in a Windows machine with node.js already installed – all I had to do was to deselect the installation of npm, and everything went fine. Nice to know that nvm can handle it too, though.

    And there’s a (hardly known) way to have multiline string in good old ES3 Javascript too: just put a backslash at the end of each line:

    var multiline = "Hello, \
    old ES6 feature";
    
  2. Madhu Rakhal Magar

    Installation goes well but when i restart the terminal in mac iojs is not available. I have to use nvm use iojs-v1.0.4 everytime. Is there is any workaround for it.

    Thanks
    Madhu Rakhal Magar

    • With nvm, you can set the node runtime to use on a per-project basis by creating an .nvmrc file in the project root containing the runtime name and version – in this case iojs-v1.0.4. Then, when in you are in the project root, you can just run nvm use to switch to that runtime.

  3. Both let and var have lexical scoping, it’s just that let has block scope and var has function scope. Both are kinds of lexical scope.

  4. Something I’m wondering about is the compatibility of the existing npm modules. I’ve heard it’s decent, but has anyone actually went ahead and ran a non-trivial nodejs app on io? It’s a bit tempting, but it seems it might not be a good idea for production just yet.

  5. I found the following sample but I m not able to find with option I have to set to make it run properly

    function Car() { 
      this.speed = 50;
    
      setInterval( () => {
        this.speed += 1; 
        console.log('this.speed ' + this.speed);
      }, 1000);
    }
    
    var car = new Car();
    

    I launch it with iojs --use_strict --harmony_arrow_functions --harmony_scoping speed.js , the script lanches by I get NaN in console.log

    • Dawn

      As I recall it. Arrow functions in V8 still doesn’t set this properly.

    • Ajay Singh

      this.speed variable scope is not defined inside the setInterval function. That’s the Reason Showing NaN.

      Following Code Works:

      "use strict"
      function Car() { 
        let a = this
        a.speed = 50;
      
        setInterval( function(){
          a.speed += 1; 
          console.log('this.speed ' + a.speed);
        }, 1000);
      }
      
      var car = new Car();
      
  6. Hurray for promises and template strings!

    You example of template strings is exactly what I was looking for:

    http://programmers.stackexchange.com/questions/274456/preserving-pre-formatted-multi-line-strings-in-node-js-scripts

  7. Is const really like let? or do you mean const is like var but the value can’t be modified.

    e.g. is const really constrained to the scope of a block?

  8. Jesse Hattabaugh

    FYI ES6 Modules still throwing a SyntaxError: Unexpected token import in iojs 3.3.0 --harmony_modules. So don’t waste your time.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!