ECMAScript 2015

con node.js

 

+GonzaloRuizdeVilla

@gruizdevilla

 

Node.js Madrid & other places

Noviembre 2014

About me:

 

Socio fundador de Adesis Netlife @adesis

Google Developer Expert en Angular

 

 

ECMAScript

a.k.a. su implementación como JavaScript

Brendan Eich inventa JavaScript 1.0

Inspiración: Scheme -> funciones           Self -> prototipos       Java -> sintaxis (ouch!)

1995

ECMAScript 3    (nuevo nombre para complacer a todos: JScript vs JavaScript)

1999

...ECMAScript 4 (en 2008 se abandona, antes de nacer)

ECMAScript 5    (mejoras varias al lenguaje, "option strict")

2009

ECMAScript 2015/ES6    (Junio 2015)

2015

ECMAScript 2016/7    (en progreso...)

???

¿Qué aporta ES6?

Cuando ves otro lenguaje y ... 

Algunas características nuevas

  • (fat) arrow functions
  • Const
  • Let
  • Default function parameters
  • Rest parameters
  • Spread
  • class
  • super
  • for of
  • generators
  • Map
  • Set
  • desestructuración
  • ...

node.js soporta ES6

Pero hay que usar flags....

--harmony

--harmony_generators
--harmony_collections 
--harmony_proxies
--harmony_modules
--harmony_scoping
--harmony_observation
--harmony_symbols
--harmony_iteration

 

 

no npm publish

No tenemos garantías de que quien use nuestro módulo use esos flags (experimentales por otra parte).

Transpilers

Source to source compilers

Programamos con ES6 y generamos ES5

El transpiler tiene las intrínsecas limitaciones del lenguaje destino: por ejemplo, WeakMap o WeakSetp

Hay otras opciones:

  • babeljs, 6to5
  • esnext
  • Google Closure Compiler
  • JsTransform
  • TypeScript 1.5 beta
  • y más

gulp.task('traceur', function () {
    return gulp.src(path.srcAndTest)
        .pipe(traceur({
          sourceMap: true,
          blockBinding: true
        }))
        .pipe(gulp.dest('dist'));
});

Tarea de gulp.js

(Fat)
Arrow
Functions


    var suma = (a, b) => a + b;
    expect( suma(1,2) ).toBe(3);
    
    expect( [1, 2, 3].reduce(suma) ).toBe(6);

    expect( [1, 2, 3].map(()=>  1) ).toEqual([1, 1, 1]);

    expect( [1, 2, 3].map((x)=> x + 1) ).toEqual([2, 3, 4]);

    expect( [1, 2, 3].map( x => x + 1) ).toEqual([2, 3, 4]);

El cuerpo puede ser:

  • Una expresión. Se devuelve el valor.
  • Una secuencia de declaraciones entre llaves. Hay que devolver valor de forma explícita.

    //declaraciones
    expect( [1, 2, 3].map(
      x => {
        var y = x + 1;
        return y * 2;
      }
    )).toEqual([4, 6, 8]);

    //una expresion
    expect( [1, 2, 3].map( 
      (x, y) => (y = x +1, y * 2) 
    )).toEqual([4, 6, 8]);

Lexical this

Bye, bye bind! (al menos en algunos casos)


      function Person() {
        this.age = 0;
        this.grow = () => this.age++;
      }
      var pedro = new Person();
      var grow = pedro.grow;
      for(var i = 0; i < 10; i++){
        grow();
      }
      expect(pedro.age).toBe(10);

Block scoped binding

Quiz

¿Cual es la salida por consola?


  var fns = [];

  for(var i = 0; i <10; i++){
     fns.push(function (){
         console.log(i);
     });
   }

   fns.forEach(x => x())

¿Y ahora?


      var fns = [];

      for(let i = 0; i <10; i++){
        fns.push(function (){
          console.log(i);
        });
      }

      fns.forEach(x => x())

const  para constantes


    // intercambiando valores sin hacer "leaking"
    // de variables auxiliares
    {
      const tmp = a;
      a = b;
      b = tmp;
    }

comprehensions

comprehensions:

Como construir un array fácilmente a partir de otro(s)

(Esto se va para 2016 como mínimo...)

forma simple



      var array = [for (x of [0, 1, 2]) x * 2];
      expect(array).toEqual([0, 2, 4]);

      var ABC = ["A", "B", "C"];
      var abc = [for (x of ABC) x.toLowerCase()];
      expect(abc).toEqual(["a", "b", "c"]) ;

forma combinada


      var nombres = ["Ana", "Elena", "Angel"];
      var apellidos = ["Gomez", "Perez"];
      var pax = [for (x of nombres) for (y of apellidos)  x + " " + y];
      expect(pax).toEqual([
        "Ana Gomez",
        "Ana Perez",
        "Elena Gomez",
        "Elena Perez",
        "Angel Gomez",
        "Angel Perez"
      ]);

como crear matrices con dos vectores


    var vector1 = [ 1, 2, 3 ];
    var vector2 = [ 3, 5, 7 ];
    var matrix = [for (i of vector1) [for (j of vector2) i * j]];
    expect(matrix).toEqual([
      [3,  5,  7],
      [6, 10, 14],
      [9, 15, 21]
    ]);

comprensiones con guardias

a.k.a. compresiones con ifs



   var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ];
   var twoDigitYear = [for (year of years) if (year > 2000) year - 2000];
   expect(twoDigitYear).toEqual([6, 10 ,14]);

resumen de comprensiones



    [for (x of iterable) x]
    [for (x of iterable) if (condition) x]
    [for (x of iterable) for (y of iterable) x + y]

for of

Lo que hemos visto es el uso de un iterador que recorre un contenedor.

 

Podemos definir los nuestros propios. 

a mano (con un objeto que devuelva Symbol.iterator)


    function pares(max) {
      return {
        [Symbol.iterator]: function() {
          var current = 0;
          return {
            next: function() {
              current += 2;
              var end = max < current;
              return {
                value: end ? undefined : current,
                done: end
              };
            }
          };
        }
      };
    }

    expect([for (x of pares(10)) x]).toEqual([2,4,6,8,10])

con generadores es más compacto

Y puedes poner varios "yield" dentro de la función


    function* pares(max) {
      var current = 2;
      while(!(max < current)){
        yield current;
        current +=2;
      }
    }

    expect([for (x of pares(10)) x]).toEqual([2,4,6,8,10])

Y se pueden evaluar a lo "lazy"

pares() es ahora infinito


    function* pares() {
      var current = 2;
      while(true){
        yield current;
        current +=2;
      }
    }

    // no hemos definido máximo, la lista es infinita

    var res = (for (par of pares()) par);
    expect(res.next().value).toBe(2)
    expect(res.next().value).toBe(4)

take some

trabajando con iterables infinitos (o no...)


      function* take(iterable, number){
        var count = 0;
        for(var taken of iterable){
          if (++count >number){
            return;
          }
          yield taken;
        }
      }
      expect([for (x of take(pares(),5)) x])
      .toEqual([2,4,6,8,10]);

Strings

el poder del back tick: `

¡multilínea!



expect(`esto es un
string que ocupa
varias lineas`)
.toEqual('esto es un\nstring que ocupa\nvarias lineas');

el poder del back tick: `

¡templates!



  var a = 1;
  var b = 2;
  expect(`${a} + ${b} = ${a+b}`).toEqual('1 + 2 = 3');

Destructuring

"Desestructurando" listas


    var [uno,,tres] = [1,2,3];
    expect(uno + tres).toEqual(4);


    var [a, [b, c], [d]] = ['hola', [', ', 'ejem'], ['mundo']];
    expect(a + b + d).toEqual('hola, mundo');

"Desestructurando" objetos



  function getPersona(){
      return {
          nombre: 'Sarah',
          apellido: 'Palmer',
          edad: 38,
          email: 'sara@palmer.me' 
      }
  }
  
  var {nombre, email} = getPersona();
  
  expect(nombre).toEqual('Sarah');
  expect(email).toEqual('sara@palmer.me');

"Desestructurando" everywhere


      
      function printName({nombre, apellido}){
          return nombre + " " + apellido;
      }
      
      var printedName = printName(getPersona());
      
      expect(printedName).toEqual("Sarah Palmer");

"Desestructurando" everywhere



    var personas = [
      {nombre: 'Pedro', apellido: 'Palmer'},
      {nombre: 'Pepa', apellido: 'Palmer'},
      {nombre: 'Paula', apellido: 'Palmer'},
      {nombre: 'Pablo', apellido: 'Palmer'}
    ]

    var nombres = personas.map(({nombre}) => nombre);

    expect(nombres).toEqual(['Pedro', 'Pepa', 'Paula', 'Pablo'])

"Desestructurando" everywhere



    var arr =  [
       {x:1, y:2},
       {x:2, y:3}
    ];

    var arr2 = arr.map(({x,y}) => x + y);

    expect(arr2).toEqual([3,5]);

"Desestructurando" everywhere



    var point = new Singularity(0,1,0, {mass:10});

    var {x,y,radius, theta, properties: {mass}} = point;

    expect(x).toEqual(0);
    expect(y).toEqual(1);
    expect(radius).toEqual(1);
    expect(theta).toEqual(Math.PI/2);
    expect(mass).toEqual(10);

Literales de objetos

Para ir un describir las cosas un poco más rápido...



    function creaMedida(descripcion, tipo, valor){
      return {
        descripcion: descripcion,
        [tipo]: valor
      };
    };
    var circulo = creaMedida('circulo', 'superficie', 3);
    var esfera = creaMedida('esfera', 'volumen', 5);

    expect(circulo).toEqual({ descripcion: 'circulo', superficie: 3 });

    expect(esfera).toEqual({ descripcion: 'esfera', volumen: 5 });

Clases



	class Point {
		constructor(x,y){
			this.x_ = x;
			this.y_ = y;
		}
		get y(){
			return this.y_;
		}
		get x(){
			return this.x_;
		}

		get theta() {
		    return Math.asin(this.y_ / this.radius)
		}
		get radius() {
			return Math.sqrt(this.x_ * this.x_ + this.y_ * this.y_)
		}
	}

Definiendo la clase

Heredando


    class Point3D extends Point{
	constructor(x, y, z){
		super(x, y);
		this.z_ = z;
	}
	get radius() {
		return Math.sqrt(super.radius * super.radius + this.z_ * this.z_);
	}
        proyectionXY(){
		return new Point(this.x, this.y)
	}
		
    }

La singularidad...



    class Singularity extends Point3D {
        constructor(x, y, z, props = {mass:0, momento: 0}) {
	    super(x,y,z);
            this.properties = props;
        }
    }

Parámetros en funciones

Default

Parámetros por defecto



    function saludo(saludado = 'Mundo'){
      return `¡Hola, ${saludado}!`;
    }
    expect(saludo() ).toEqual('¡Hola, Mundo!');
    expect(saludo('Lucia')).toEqual('¡Hola, Lucia!')

Rest




   class Tienda {
      constructor(){
        this.escaparate = {};
      }
      add(categoria, ...productos) {
        this.escaparate[categoria] = this.escaparate[categoria] || [];
        productos.forEach(producto => this.escaparate[categoria].push(producto));

      }
    }
    var tienda = new Tienda();

    tienda.add('fruta', 'manzanas', 'peras');

    expect(tienda.escaparate.fruta).toEqual(['manzanas', 'peras']);

Spread

La inversa de Rest



    var productosCarnicos =  ['ternera', 'pollo', 'pavo'];

    tienda.add('carne', ...productosCarnicos);

    expect(tienda.escaparate.carne).toEqual(productosCarnicos);

O más habitual


  expect(
    Math.max(...[1,2,3,4,5])
  ).toEqual(5);

Módulos


  // math.js
  export var suma = (a,b)  => a + b;
  export var e = Math.E;


  // app.js
  import * as math from 'math';

  expect( math.suma(1, 2) ).toBe(3);
  expect( math.e ).toBe(Math.E);


  // app.js
  import {suma, e} from 'math';

  expect( suma(1, 2) ).toBe(3);
  expect( e ).toBe(Math.E);


   import {Point, Point3D, Singularity} from '../puntos';

   var punto = new Point()



    export class Point {
        //..
    }

    export class Point3D extends Point{
        //...
    }


    export class Singularity extends Point3D {
        //...
    }

Nuevos objetos

Map



    class Bote {

    }
    var bote = new Bote();
    var cosas = new Map();
    
    cosas.set(bote, 'mas cosas')


    expect(cosas.get(bote)).toEqual('mas cosas');

Set



    var cosas = new Set();
    cosas.add('lapices').add('sacapuntas').add('lapices');

    expect(cosas.size).toEqual(2);
    expect(cosas.has('lapices')).toBeTrue()

Symbol



    var norte = Symbol();
    var este = Symbol();
    var oeste = Symbol();
    var sur = Symbol();
    function mira(direccion) {
      var direcciones = {};
      direcciones[norte] = true;
      direcciones[este] = true;
      direcciones[oeste] = true;
      direcciones[sur] = true;
      return direcciones[direccion]
    }
    expect(mira(1)).toBeFalsy();
    expect(mira(sur)).toBe(true);

WeakMap

WeakSet

¡Recursividad!

Proper Tail Calls

 

y no "Tail Call Optimization" o TCO:

las optimizaciones son transparentes y afectan a la eficiencia. Si la llamada no es "proper" no hay reutilización de callstack y el programa puede fallar, no es que sea más lento,


  //AVISO: no funciona con arrays con 'undefined' en alguna posición
  const reduce = 
    (fn, acc, [x, ...xs]) =>
      x === undefined ? acc
                      : reduce(fn, fn(acc, x), xs);

  //------------------------------------------------------//

  const map =
    (fn, xs) =>
      reduce(
        (acc, x) => [...acc, fn(x)],
        [],
        xs
      );

  var oneArr = [1];
  var addOne = (arr) => oneArr.concat(arr);
  var sum = (a,b) => a + b;

  var thousandOnes = reduce(addOne, [], new Array(1000));
  expect(reduce(sum, 0, thousandOnes)).toEqual(1000);

Prometiendo


  function timeout(ms) {
    return new Promise((resolve) => {
      setTimeout(resolve, ms);
    });
  }

  timeout(10).then(() => done());

Promesas nativas

Simplificando el código asíncrono:
async & await

 

Una función "async" puede contener expresiones "await".


El operando de un "await" se trata como una Promesa, y cuando esta se completa la ejecución continúa.


  function timeout(ms) {
    return new Promise((resolve) => {
      setTimeout(resolve, ms);
    });
  }

  async function asyncValue(value) {
    await timeout(50);
    return value;
  }

  (async function() {
    var value = await asyncValue(42)
                      .catch(console.error.bind(console));
    assert.equal(42, value);
    done();
  })();

ECMAScript Tools by @addyosmani

https://github.com/addyosmani/es6-tools

  • Transpilers
  • Grunt, Gulp & Broccoli tasks
  • Module Loaders
  • Boilerplate
  • Code Generation
  • Polyfills
  • Editores
  • Parsers
  • y mas...

ECMAScript 6 compatibility table

http://kangax.github.io/compat-table/es6/

ES6 con node.js

By Gonzalo Ruiz de Villa

ES6 con node.js

ECMAScript 6 con node.js. Cosas que puedes hacer con Traceur

  • 5,103