Introducción a ECMAScript 2016/7

Desde finales del 2014, incluso antes de que se terminara de definir ECMAScript 6 ya se estaba empezando a trabajar en la siguiente versión de ECMAScript, la versión 7, formalmente conocida como ECMAScript 2016, ya que se espera este lista en el 2016.

Al igual que ES6 en ES7 se van a venir varias incorporaciones al lenguajes, algunas van a ser nuevas funcionalidades y otras van a ser syntax sugar de características ya existentes.

Nota: Este artículo esta desactualizado, no todas las características aquí explicadas fueron incorporadas finalmente en ECMAScript 2016, algunas serán implementadas en ECMAScript 2017 o en el futuro. Además la versión de Babel usada al final esta desactualizada.

Async/Await

La primera de las nuevas características que trae ES7 es conocida como Async/Await o simplemente funciones asíncronas. Estas nuevas funciones están construidas combinando las Promesas y los Generadores de ES6.

La idea de las funciones asíncronas es tener una forma más fácil de definir funciones que trabajen de forma asíncrona y permitir a los developers usar try/catch, return y throw como si fuesen funciones síncronas.

Para poder crear una función asíncrona necesitamos agregar la palabra clave async, esto ya convierte nuestra función en una función asíncrona y nos habilita a usar la palabra clave await dentro de nuestra función.

Await detiene la ejecución de nuestra función asíncrona hasta que otra función asíncrona se complete, para esto agregamos await adelante del nombre de la función a ejecutar, es necesario que la función que esperamos devuelve una promesa.

import fetch from "whatwg-fetch";

async function getData() {
  try {
    const response = await fetch("/api/resource");
    return response.body;
  } catch (error) {
    throw error;
  }
}

getData()
  .then(::console.log)
  .catch(::console.error);

En este ejemplo podemos ver una función asíncrona llamada getData la cual intenta hacer una petición AJAX usando fetch a /api/resource, si la petición devuelve un error se realiza un throw del error, en caso de que se complete correctamente la respuesta de la petición se asigna a la constante response y luego devolvemos el cuerpo de la respuesta.

Por último ejecutamos nuestra función asíncrona, todas las funciones asíncronas devuelven una promesa y podemos trabajar sobre estas como cualquier promesa, ya sea usando then y catch o ejecutándola desde otra función asíncrona como hicimos con fetch.

Esta funcionalidad ya esta soportada por defecto en Babel.js.

Function Bind Syntax

Esta nueva característica consiste en un syntax sugar para la forma en que hacemos call sobre una función para ejecutarla y hacer un bind del this. Con esta nueva sintaxis realizar un call es tan simple como escribir lo que queremos usar de this seguido de :: y la función que queremos ejecutar, pasándole cualquier parámetro que queramos.

Para entenderlo bien veamos un ejemplo de la forma actual:

const inputs = document.querySelectorAll("input");
const map = Array.prototype.map;
const values = map.call(inputs, element => element.value);

En este código estamos obteniendo un listado de todos los input del sitio, esto nos devuelve un objeto que es una instancia de un NodeList, al no ser una instancia de un Array no tenemos acceso a los métodos de estos.

Así que para poder mapear nuestro NodeList y obtener un nuevo Array con los valores de cada input estamos haciendo un call sobre el método map extraído del prototipo de un Array para ejecutarlo indicándole que use nuestro NodeList como su this y ejecute nuestra función que devuelve el value de cada input.

Ahora este mismo código realizado con la nueva sintaxis sería así:

const inputs = document.querySelectorAll("input");
const map = Array.prototype.map;
const values = inputs::map(element => element.value);

Como se puede ver la nueva sintaxis nos permite ejecutar la función map de forma similar a un método, solo que en vez de usar . estamos usando ::. Esto ayuda a que sea más fácil entender lo que se esta realizando en el código

Operador exponencial **

Esta es bastante simple, desde ES7 va a ser posible usar ** para elevar un número X a una potencia Y. Haciendo una comparación con la forma actual:

const result = Math.pow(2, 3);

Forma nueva:

const result = 2 ** 3;

Esta funcionalidad ya esta soportada por defecto en Babel.js.

Array.prototype.includes

Este nuevo método de los Array es un syntax sugar que nos permite fácilmente saber si un elemento X esta dentro de un array, algo que actualmente haríamos con un indexOf de la siguiente forma:

const isInArray = [1, 2, 3].indexOf(2) !== -1;

Con el nuevo método quedaría más fácil de entender que hace el código:

const isInArray = [1, 2, 3].includes(2);

Al igual que con el operador :: esto hace más fácil de entender que hace el código.

Object.observe y Array.observe

Una de las características nuevas más interesantes, la cual incluso ya funciona en Chrome y Opera, es Object.observe y su equivalente Array.observe. Este método de los objetos Object y Array (en sus respectivas instancias no existen) nos permite definir funciones que se queden escuchando cada vez que haya un cambio sobre un objeto o array sin necesidad de que estos tengan un eventListener definido.

Para poder usar este método es simplemente ejecutarlo pasándole dos parámetros, el primero es el objeto o array a observar y el segundo es la función que se va a ejecutar cuando haya cambios, esta función va a recibir un array de todos los cambios que se hayan realizado.

const persona = {
  nombre: "Freddy",
  empresa: "Mejorando.la"
};

Object.observe(persona, changes => {
  changes.forEach(console.log);
});

setTimeout(() => (persona.empresa = "Platzi"), 2500);

En el ejemplo anterior creamos un objeto persona con dos propiedades, luego empezamos a escuchar los cambios que se realicen sobre este objeto y ejecutamos una función que va a recorrer el listado de cambios que recibimos y va a mostrarlos en consola. Por último después de 2.5 segundos modificamos una de las propiedades de nuestro objeto.

Ahora en nuestra consola vamos a ver un objeto similar a este:

{
  "name": "empresa",
  "object": {
    "nombre": "Freddy",
    "empresa": "Platzi"
  },
  "oldValue": "Mejorando.la",
  "type": "update"
}

Acá podemos ver el nombre de la propiedad que cambio, el estado del objeto actualmente con los cambios aplicados, el valor anterior de la propiedad que cambio y el tipo de cambio que se realizo.

En este caso el tipo fue update esto quiere decir que una de las propiedades del objeto se modificó, otros posibles tipos de cambios son add cuando se agrega una nueva propiedad (en este caso no existe la propiedad oldValue) y el otro es delete cuando se borra una propiedad del objeto.

El uso de Object.observe y Array.observe nos permitiría hacer data binding sobre objetos y arrays facilmente sin necesidad de librerías y combinado con MutationObserver sería posible hacer un two-way data binding sin necesidad de hacer dirty checking como hace Angular v1.x.

Como detalle extra, Polymer usa Object.observe internamente y carga un polyfill para que funciones en otros navegadores que no sean Chrome y Opera.

Propiedades de clases

En ES6 se incorporaban clases al lenguaje, aunque estas son en realidad un simple syntax sugar de la forma tradicional de hacer POO en JS. Algo que no se podía hacer con las clases de ES6 es agregar propiedades a la clase directamente, en lugar de eso había que hacer algo así:

class Persona {
  saludar() {
    return `Hola soy ${this.nombre}`;
  }
}
Persona.nombre = "Sergio";

En ES7 se agrega una mejora a estas clase permitiéndonos agregar propiedades directo dentro de la definición de la clase de la siguiente forma:

class Persona {
  nombre = "Sergio";
  saludar() {
    return `Hola soy ${this.nombre}`;
  }
}

Decoradores

Otra mejora relacionada a las clases es la implementación de decoradores que nos permiten extender la funcionalidad de una clase en tiempo de ejecución fácilmente.

Para esto una vez tenemos nuestra clase (como la definida en el punto ejemplo anterior) podemos crear la función decoradora y aplicársela a la definición de la clase agregando @nombre antes de la definición de la clase.

function agregarDespedida(target) {
  target.prototype.despedirse = persona => {
    return `Adios ${persona.nombre}!`;
  };
  return target;
}

@agregarDespedida
class Persona {
  nombre = "Sergio";
  saludar() {
    return `Hola soy ${this.nombre}`;
  }
}

const persona = new Persona();

console.log(persona.despedirse(persona));

Esta nueva funcionalidad en JS nos permite trabajar de forma más interesante con las clases incorporadas en ES6 para poder hacer código más reutilizable.

Usando ES7 actualmente

Al igual que con ES6 ya es posible empezar a usar estas nuevas características en nuestro código gracias a Babel.js, sin embargo Babel por defecto no tiene activado el soporte a ES7.

Para poder activarlo es necesario crear un pequeño archivo llamado .babelrc en la raíz de nuestro proyecto el cual Babel.js (ya sea que usemos directamente Babel o usemos gulp-babel o babelify) va a leer y usar como archivo de configuración. En este archivo necesitamos colocar el siguiente texto:

{
  "stage": 0
}

Con eso activamos el soporte a todas las características de ES7 que Babel.js ya soporta, de las anteriores solamente no incluye soporte a Object.observe y Array.observe.