Américo Vespúcio uma vez chamou a América de “Novo Mundo” quando ela ainda não era conhecida pelos nossos ancestrais. Essa é a mesma impressão que tive com a especificação do ES2015 (ECMA-262, 6th Ed.) e o novo panorama que ela traz. Tem várias coisas por vir e elas vão se manter apenas como exemplos até que a gente realmente as utilize em algum projeto. Eu sei como frameworks de testes unitários funcionam – eu sou um colaborador do QUnit – então usei esse conhecimento para explorar esse novo ambiente.

Estava conversando com alguns amigos na Bocoup sobre a sintaxe e imaginei como isso funcionaria. Logo, em cerca de três horas e meia, depois de 11 da noite de um sábado de inverno, eu crei o Goiabada, um framework assíncrono de testes unitários que utiliza várias das novas características, incluindo:

  • Módulos
  • Classes
  • Promises
  • Generators (++ para essa que foi incrível para resolver a fila de testes assíncronos)
  • Arrow functions
  • Template strings
  • Parameters destructuring
  • Shorthand properties

Eu acho bacana falar de algumas das características que mais tiveram impacto no código do projeto.

Módulos e Classes

Sendo bem honesto, eu não imaginava que usar class seria interessante. Quando vi eu pensei que JS poderia se tornar uma linguagem mais verbosa sem nenhum bom propósito. Ao invés disso, eu provei a mim mesmo que estava errado. Elas foram ótimas para criar módulos separados em código legível.

Desenvolvedores JavaScript têm aguardado durante muito tempo por um padrão definitivo de módulos. Você já deve ter utilizado os formatos AMD ou CommonJS, mas agora o ES2015 soluciona esse problema com uma definição final de módulos nativos e com sintaxe simples.

O Goiabada foi estruturado em três arquivos diferentes, todos eles são módulos que exportam uma classe com seus respectivos nomes:

  • goiabada.js: o módulo principal
  • assert.js: para controlar o objeto de asserções
  • logger.js: responsável para registro de eventos

E eles se pareciam assim:

// Estrutura básica do Goiabada
export default class Goiabada {
  constructor() {
    ...
  }

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

Dessa forma, foi possível simplesmente importar um módulo e instanciá-lo em um novo objeto.

import Goiabada from 'Goiabada';

var goiabada = new Goiabada;

Todo o código que estava fora da classe, mas no mesmo módulo, era referente a valores privados, como métodos, variáveis e constantes. Não foi preciso de nenhum hack para proteger o escopo.

Promises e Generators

Promises e Generators foram excelentes para criar uma fila de testes e manter a ordem conforme eles eram inseridos. O código foi super simples e resolveu um problema que já foi muito mais complexo com ES5. Para cada teste terminado, a função generator era invocada, chamando o próximo teste na fila.

Eu também utilizei outra promise para dizer quando o bloco de teste havia finalizado. Dessa forma, eu retornei uma promise informando quantas asserções haviam sido executadas, quantas passaram e quantas falharam.

Pode soar estranho ter duas promises próximas (uma para controlar a fila de testes e outra para responder o bloco de testes) mas ainda tenho planos para melhorar isso em um próximo refactoring.

Abaixo tem um exemplo de como utilizei as promises:

// Variáveis são restritas ao escopo do módulo
var running;

export default class Goiabada {
  ...

  test( name, callback ) {

    // Retorna essa *promise* para informar quando seus testes acabaram.
    return new Promise( testResolve => {
      ...

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

A classe Assert é responsável para resolver as promessas internas dos testes. Quem for escrever os testes pode expressar que as asserções em um teste terminam de duas maneiras: invocando o método end() ou chamando o número de asserções especificadas pelo expect().

goiabada.test( "teste assíncrono finalizado após asserções esperadas", assert => {
  assert.expect( 2 );

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

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

goiabada.test( "teste assíncrono finalizado pelo end()", assert => {
  setTimeout(() => {
    assert.ok( true );

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

});

Arrow functions

Essa é a minha característica favorita no ES6: uma sintaxe menor de função e uma atribuição de this léxico.

A definição de léxico pode não ser fácil de entender, especialmente quando todas as funcões que você já escreveu tem uma atribuição de this dinâmico. O MDN tem um bom exemplo, mas ele é um pouco artificial. Um exemplo do código do Goiabada pode ser um pouco mais realístico:

export default class Goiabada {
  ...

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

        // o this se refere ao objeto instanciado do Goiabada
        // ( this instanceof Goiabada ) === true
        this.queue.push( { name, callback, testResolve } );

        ...
      });
  }
}

Uma função comum naquela promise criaria um this dinâmico, referenciando ele ao objeto global. Nesse caso, eu precisaria criar uma outra atribuição para referenciar o objeto instanciado:

export default class Goiabada {
  ...

  test( name, callback ) {

    var self = this;

    return new Promise( function( testResolve ) {

        // o this se refere ao objeto global
        // ( this instanceof Goiabada ) === false

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

        ...
      });
  }
}

Arrow functions estão por todo o código do Goiabada. Você pode não precisar de um this dinâmico a todo momento, mas uma sintaxe mais curta é bem útil. Falando de sintaxe curta…

Sintaxe curta

Também há outras características que funcionaram muito bem ao permitirem um código menor de fácil compreensão.

Eu tenho esse exemplo da função error no meu módulo Logger. Ao invés de lidar com o objeto arguments não explícito, eu simplesmente busco os argumentos em um rest parameter chamado args, então eu defino uma variável via let limitado ao escopo do bloco do loop para cada item em args utilizando o for of.

export default class Logger {
  ...

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

Eu não sinto a menor falta de uma função como essa:

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

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

Esses exemplos mostram como as próximas características não são apenas interessantes, mas elas tornam o nosso trabalho muito mais fácil com um código mais enxuto. O Rick Waldron escreveu sobre isso em The Little JavaScripter Revisited.

Conclusão

Goiabada ainda está incompleto e tem muito trabalho lá para torná-lo em algo mais do que um simples protótipo. Ainda assim, foi muito importante pra eu brincar com as novas funcionalidades do ES6.

Brincando apenas em temas isolados não funciona muito bem para criar experimentos funcionais. Criar projetos completos utilizando essas características é, em minha sincera opinião, a melhor maneira de aprendê-las e como elas podem funcionar em conjunto. Depois de aprender as boas partes de todas, você pode trazê-las aos seus outros projetos naturalmente.

Eu ficaria feliz de saber que você também tentou novos projetos experimentais utilizando essas novas features. Me avise se o código está no Github ou em qualquer outro tipo de repositório. Estamos prestes a descobrir um novo mundo para as nossas habilidades com o JavaScript. Assim como Américo Vespúcio, vamos explorá-lo!