A finales de Junio se aprobó el nuevo estándar de Javascript, ES2017, popularmente conocido como ES8. Como siempre, te voy a explicar todas las novedades de forma detallada para que puedas estar a la última 😉

¿Cuales son las novedades?

Seguramente sabrás que desde 2015 se acordó actualizar el estándar de Javascript cada año para poder mantener JS al día de forma suave. Los cambios del año pasado fueron muy descafeinados, pero este año tenemos alguna novedad más jugosa.

Listando rápidamente las novedades, son:

  • Flujo asíncrono de funciones con async y await
  • Object.entries y Object.values
  • Listar propiedades con Object.getOwnPropertyDescriptors
  • Padding de Strings
  • Memoria compartida y acceso atómico
  • Comas al final en listas de parámetros y llamadas a funciones

Funciones async y expresión await

La killer feature de ES8 y la novedad más esperada.

Las funciones async simplifican el control de flujos asíncronos, evitando anidar Promise.then continuamente y reduciendo la cantidad de código.

Al declarar una función con el modificador async, obtienes un objeto AsyncFunction, que consiste en un bloque de código que se ejecutará de forma asíncrona. Cuando llamas a la AsyncFunction, obtienes siempre una promise.

La gracia de las funciones async es que pueden contener expresiones await, que se usan para pausar la ejecución de la función async hasta resolver una promise, devolviendo su resultado.

Te lo enseño con un ejemplo que lo entenderás mejor: Voy a crear un par de promises, y luego una función async que usará expresiones await para controlar el flujo de las promises anteriores.

//promise (ES6) que devuelve un usuario de forma asíncrona
function fetchUser(authToken) {
  return new Promise(resolve => { 
    setTimeout(() => {  resolve({name: "Chuck Norris"}); }, 2000);
  });
}

//promise (ES6) que valida un usuario y devuelve su token de autenticación en caso correcto, o un error en caso contrario.
function authUser(id, pwd) {
  return new Promise((resolve, reject) => { 
    setTimeout(()=>{
      if(pwd == 'pass'){
        resolve({authToken: "1234"});
      }else{
        reject("invalid password");
      }      
    }, 2000);
  });
}

/* ---------------------- */
/* Aquí empieza la chicha */
/* ---------------------- */

//async function (ES8), hace un login (recibe id y pwd) y devuelve el usuario
async function login(id, password){
  try{
    //await (ES8): para la función hasta resolver authUser
    const authToken = await authUser(id, password);

    //await (ES8): para la función hasta resolver fetchUser    
    const user = await fetchUser(authToken);

    //finalmente devuelvo el usuario
    return user;
  }
  catch(err){
    //puedo lanzar errores, que la async function devolverá como "rejects".
    throw new Error(err);
  }
}

Fíjate cómo la función async de login utiliza await para simplificar el mecanismo de espera de resolución con las promises authUser y fetchUser.

Además, podría usar esta función de login como una promise cualquiera.

login('Chuck Norris', 'pass')
  .then(user => console.log(user))
  .catch(err => console.log(err));

console.log("end");

//la salida por consola será:
// -> end
// ---- 4s ---> Chuck Norris

Claro, login funciona de forma asíncrona y como tarda 4 segundos (2 para la autenticación y 2 para el fetch de usuario), se loguea antes por consola el mensaje «end«.

Por supuesto, si el password fuera incorrecto, la función de login lanzaría un error que sería detectado en el catch.

login('Chuck Norris', 'NO-pass')
.then(user => console.log(user))
.catch(err => console.log(err));

console.log("end");

//la salida por consola será:
// -> end
// ---- 2s ---> invalid password

IMPORTANTE: La expresión await solo se puede usar dentro de funciones async.

Además, la sintaxis de clases introducida con ES6, también permite métodos async. El ejemplo anterior podría meterse en una clase, de la siguiente forma:

//async - await syntax, in ES6 classes
class AuthService{
  fetchUser(authToken) { /*...*/ }

  authUser(id, password) { /* ...*/ }

  async login(id, password){
      const authToken = await this.authUser(id, password);
      const user = await this.fetchUser(authToken);
      return user;
  }
}


let auth = new AuthService();

auth.login('Chuck Norris', 'pass')
.then(user => console.log(user))
.catch(err => console.log(`err is ${err}`));

Object.entries y Object.values

Se añaden estos dos nuevos métodos a la clase Object para iterar sobre las propiedades enumerables propias de un objecto. El resultado sigue la misma ordenación que los loops for in.

Los nombres de los métodos son bastante autoexplicativos. Aquí tienes un ejemplo de lo que hacen:

const data = { a: 'hola', b: 1 };
Object.values(data); // ['hola', 1]
Object.entries(data); // [['a', 'hola'], ['b', 1]]

Recuerda que los arrays tratan sus elementos como un objeto con propiedades numéricas empezando en cero.

const obj = ['a', 'z', '5']; // equivale a { 0: 'a', 1: 'z', 2: '5' };
Object.values(obj); // ['a', 'z', '5']
Object.entries(obj); // [['0','a'], ['1','z'], ['2','5']]
Object.values('az5'); // ['a', 'z', '5']
Object.entries('az5'); // [['0','a'], ['1','z'], ['2','5']]

Además, for in ordena las claves numéricas en orden ascendente.

const obj = { 2: 'a', 1: 'b', 3: 'c' };
Object.values(obj); // ['b', 'a', 'c']
Object.entries(obj); // [['1','b'], ['2','a'], ['3','c']]

Padding de Strings

El objeto string recibe estos 2 nuevos métodos para rellenar un string, ya sea por delante (padStart) o por detrás (padEnd), con el objetivo de alcanzar un tamaño determinado.

Además, le puedes pasar un string con el contenido de relleno (en caso contrario, usa espacios).

Así se usa padStart

'bar'.padStart(1);          // 'bar'
'bar'.padStart(4);          // ' bar'
'bar'.padStart(4, 'foo');   // 'fbar'
'bar'.padStart(10, 'foo');  // 'foofoofbar'
'bar'.padStart(5, '0');     // '00bar'

Así se usa padEnd

'foo'.padEnd(1);          // 'foo'
'foo'.padEnd(4);          // 'foo '
'foo'.padEnd(4, 'bar');  // 'foob'
'foo'.padEnd(10, 'bar');  // 'foobarbarb'
'foo'.padEnd(5, '1');     // 'foo11'

Object.getOwnPropertyDescriptors

El método getOwnPropertyDescriptors devuelve todos los descriptores de propiedades propias del objeto (no heredadas por su prototype ). Este método nace con la voluntad de facilitar el clonado entre 2 objetos, que es algo que hasta el momento había que hacer a mano en Javascript.

Te dejo un par de ejemplos de mecanismos comunes que se ven beneficiados de este método:

const shallowClone = (object) => Object.create(
  Object.getPrototypeOf(object),
  Object.getOwnPropertyDescriptors(object)
);

const shallowMerge = (target, source) => Object.defineProperties(
  target,
  Object.getOwnPropertyDescriptors(source)
);

Además del clonado de objetos, este método va a ser de especial interés en un futuro, cuando se aprueben los decoradores (o ahora mismo si trabajas con TypeScript), ya que los descriptores de propiedades se usan habitualmente en su definición.

Memoria compartida con acceso atómico

Si has trabajado en lenguajes multithread como C, entenderás perfectamente el concepto de acceso atómico.

Básicamente, cuando 2 threads tienen acceso a un espacio de memoria compartido, es importante que las operaciones de escritura o lectura sean atómicas, es decir, se hagan en bloque y sin interrupciones de otros threads, para garantizar la validez de los datos.

Esta funcionalidad está pensada para web workers, ya que es el único caso en el que te vas a encontrar multithreading en Javascript, y de momento está orientada a muy bajo nivel (ArrayBuffers: arrays de datos binarios).

El mecanismo consta de:

  • SharedArrayBuffer: Un nuevo constructor para crear ArrayBuffers de memoria compartida.
  • La clase Atomic, con métodos estáticos para acceder/manipular ese SharedArrayBuffer de forma atómica.

Esto es algo que seguramente no usarás en tu día a día. Por si lo necesitaras, puedes encontrar más detalles aquí.

Comas al final de parámetros y funciones

Hasta ahora, si tras el último parámetro en la definición de una función o en su llamada, metías una coma… ¡¡¡BIIIP!!! ERROR, ¡te saltaba una excepción!

A partir de ES8, Javascript ya no se quejará por esas comas de más, sencillamente las ignorará:

//trailing commas in declaration
function foo(a, b, c,) { /* ... */ }

//trailing commas in execution
foo("x", "y", "z",);

Conclusiones

La inclusión de las async functions era una de las novedades más esperadas para este año, y estoy contento de que se haya incorporado por fin a Javascript. Me he quedado con las ganas, eso sí, de que se incluyesen también los decoradores, a los que tanto partido les saco en TypeScript. El resto (salvo lo del acceso atómico) son pequeñas utilidades que se usan de forma habitual y te van a ahorrar varias líneas de código 😉

Si me tengo que mojar de cara al año que viene, mi apuesta es que la nueva killer feature será, por fin, los decoradores.

¿Y tú? ¿Cual crees que será la novedad principal para ES9?