Practicing the new world language skills

Amerigo Vespucci once called the Americas the “New World”, as they weren’t known to our ancestors. That’s the same impression I’ve had of the forthcoming ES2015 (ECMA-262, 6th Ed.) specification and the newly evolved landscape that it creates. There are a lot of things to come and they’ll remain only examples until we really use them in a project. I know how unit test frameworks should work – I’m a QUnit collaborator – so I used this knowledge to have solid land for this exploration.

I was talking with some friends at Bocoup about the syntax and I wondered how would it work, and then, in about 3.5 hours, starting at 11PM of a winter Saturday, I built Goiabada *, an async unit test framework that takes advantage of the new capabilities.

I made Goiabada using several new features, including:

  • Modules
  • Classes
  • Promises
  • Generators (++ to this one as it solved the async tests order like a charm)
  • Arrow functions
  • Template strings
  • Parameters destructuring
  • Shorthand properties

I’d like to talk about the features that had the biggest impact on the code base.

Modules and Classes

To be quite honest, I wasn’t aware of the good of the class form. I just saw it and imagined how JS would become like a more verbose language without adding meaning. But instead, I proved myself wrong. They were nice for building the individual modules in legible code.

JavaScript developers have been waiting for a definitive module pattern for a long time. You might have used the AMD or the CommonJS module formats, but now ES6 comes to the rescue with a native, clean syntax for defining modules.

Goiabada was structured with three different files, all of them were modules that exported a class with their respective names:

  • goiabada.js: the core module
  • assert.js: to control the assertion object
  • logger.js: responsible to log events

And they looked like:

// Goiabada basic class structure
export default class Goiabada {
  constructor() {
    ...
  }

  test( name, callback ) {
    ...
  }
}

This way, it was possible to simply import a module and instantiate a new object from it.

import Goiabada from 'Goiabada';

var goiabada = new Goiabada;

Everything out the class was taken as private values, like methods, variables and constants. No hacking for scope protection.

Promises and Generators

Promises and Generators did an amazing job creating a test queue and respecting the order. The code turned to be very simple and solved a problem once complex with ES5. For each finished test, the generator function got triggered, calling the next test in the queue.

I used another promise to tell when the test block was done. This way, I returned a promise to tell how many assertions were run, how many passed and how many failed.

It might sound weird to use two promises there (one to control the test queue and another to answer to the test block) but I plan to improve this in a future refactoring.

Below is an example of what I did with promises:

// Variables are restricted to the module scope
var running;

export default class Goiabada {
  ...

  test( name, callback ) {

    // Return this promise, so you can check stats when your tests are done
    return new Promise( testResolve => {
      ...

      if ( !running ) {
        // runner is the generator at Goiabada's private scope
        running = runner.call( this );
        ...
      }
    });
  }
}

The Assert class is responsible for resolving test promises. Test authors can express that their tests are complete in one of two ways: by calling the end() method or by making the number of assertions specified to expect().

goiabada.test( "async test ended after expected assertions", assert => {
  assert.expect( 2 );

  setTimeout( () => {
    assert.ok( true, "foo" );
  }, 50 );

  setTimeout( () => {
    assert.ok( true, "bar" );
  }, 50 );
});

goiabada.test( "async test ended through end()", assert => {
  setTimeout(() => {
    assert.ok( true );

    // ends the assertion block
    assert.end();
  }, 50 );

});

Arrow functions

This is my favorite ES6 feature: shorter syntactical function form and lexical this binding.

The lexical definition might not be easy to understand, especially when all the functions you’ve ever written have a dynamic this binding. MDN has a nice example, but it is a little artificial. An example from Goiabada’s source might be more realistic:

export default class Goiabada {
  ...

  test( name, callback ) {
    return new Promise( testResolve => {

        // this refers to the instantiated Goiabada object
        // ( this instanceof Goiabada ) === true
        this.queue.push( { name, callback, testResolve } );

        ...
      });
  }
}

A regular function on that Promise would give me a dynamic this binding, referencing it to the global object. In that case, I would have to create a new binding for the reference to the instance:

export default class Goiabada {
  ...

  test( name, callback ) {

    var self = this;

    return new Promise( function( testResolve ) {

        // this refers to the global object
        // ( this instanceof Goiabada ) === false

        self.queue.push( { name, callback, testResolve } );

        ...
      });
  }
}

Arrow functions are everywhere in Goiabada’s code. You might not need a dynamic this binding all the time, but the shorter syntax is useful on its own. Speaking of shorter syntax…

Shorter syntax

There are some other new features that work really nice by allowing you to write less code while keeping it very easy read and understand.

I got this example with the error function on my Logger module. Instead of dealing with the non explicit arguments object, I simply store all arguments in a rest parameter called args, and than I define with let a block scoped variable to loop for each item of args.

export default class Logger {
  ...

  error( ...args ) {
    for ( let item of args ) {
      console.error( item );
    }
  }
}

I don’t miss anything about a function like this:

function error() {
  var args = [].slice.call( arguments );
  var item;
  var i;

  for ( i = 0; i < args.length; i++ ) {
    item = args[ i ];
    console.error( item );
  }
}

Those examples show how the next features are not only interesting, but they make our jobs easier with a cleaner code. Rick Waldron wrote about this in The Little JavaScripter Revisited.

Conclusion

Goiabada is still incomplete and there’s a lot of work there to make it more than a prototype. Still, it was very important for me to play with the new features.

Playing in isolated test fields doesn’t work as well as making functional experiments. Creating whole new projects using those features is (in my humble opinion) the best way to learn those features and how they might work together. After learning the good parts of everything, you can bring them to your other projects naturally.

I would be glad to learn how you also tried new experimental projects using the new features, let me know if the source is on Github or any other git friendly website. We’re about to discover a new world for our JavaScript skills. Just like Amerigo Vespucci, let’s explore it!


* – Goiabada is a delightful guava jelly, very popular in Brazil and a perfect combination if you try it with a white and salty cheese. It’s as sweet as writing a framework using the new specs and all of that syntactic sugar.

Comments

Contact Us

We'd love to hear from you. Get in touch!

Phone

+1 617-283-2807

Mail

P.O. Box 961436
Boston, MA 02196