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 funcionesasync
.
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 crearArrayBuffers
de memoria compartida.- La clase
Atomic
, con métodos estáticos para acceder/manipular eseSharedArrayBuffer
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?