Como hemos comentado en algún post previo sobre las especificaciones de ECMASCript, el TC39, es el comité que propone nuevas funcionalidades al stándar mediante un proceso que cuenta con 5 stages por las que toda propuesta de nueva funcionalidad tiene que pasar hasta alcanzar el stage 4, que es el stage final. Adicionalmente las funcionalidades seleccionadas pasan, en la mayoría de los casos, a formar parte de la especificación ECMAScript seguida del nombre del año, ya que la nomenclatura de ECMAScript 5, ECMAScript 6, etc y que hemos usado durante años previos ya no volverá a ser usada. Aunque aún no está totalmente cerrada la lista completa de funcionalidades que serán incluídas como parte del posible ECMAScript 2020 o ES2020, ya hay una lista bastante interesante de funcionalidades finalistas que merece la pena examinar.
¿Podemos probar ya estas nuevas funcionalidades? Sí, y para probar la gran mayoría de ellas no necesitamos nada más que un navegador Chrome actualizado, pues Chrome suele dar soporte a la mayoría de funcionalidades en cuanto llegan a stage 3, por lo que cuando llegan a stage 4 y son parte del estándar Chrome ya suele soportarlas, aunque existe algún caso en el que puede ser necesario un plugin de Babel.
Sin lugar a dudas una de las funcionalidades más esperadas e interesantes. Los módulos que importamos habitualmente en nuestras aplicaciones son totalmente estáticos, es decir, no tenemos la capacidad de que el nombre de dicho módulo sea dinámico, no pudiendo realizar cargas condicionales. Con import esto va a cambiar ya que finalmente, vamos a poder tener nombres de módulo totalmente dinámicos, pero no solo eso si no que además import nos devuelve una promesa:
import(`./my-modules/${dynamicModuleName}.js`)
.then((module) => {
module.doStuff();
})
.catch((err) => console.log(err));
Como vemos con import podemos controlar el resultado de la importación de dicho módulo y además dado que retorna una promesa podemos utilizar async/await para la carga de los módulos.
const getModule = (module) => `./my-modules/${module}.js`;
const asyncModuleLoad = async () => {
const { fn1, fn2 } = await import(getModule("module1"));
const module2 = await import(getModule("module2"));
module2.doStuff();
};
Si siempre has encontrado sumamente tedioso la tarea de comprobar la existencia de una propiedad antes de poder acceder a sus subpropiedades para evitar el TypeError "can't access x of undefined" estás de suerte. Con optional chaining esto es historia ¡al fin!. Con el operador "?" podemos indicar que una propiedad puede existir o no sin tener que validar su existencia previamente y asi evitar el TypeError en caso de que no exista, dejando un codigo mucho más limpio:
//Tedioso... const departmentName = employee.department && employee.department.name; //Optional chaining! const departmentName = employee.department?.name;
Flat nos permite coger un Array multidimensional, es decir, un Array de Arrays y convertirlo (aplanarlo) en un Array unidemensional de forma rápida y sencilla, pudiendo además, elegir cuantos niveles de profundidad queremos aplanar:
const nestedArray = [1, 2, [3, 4]];
const flattened = nestedArray.flat();
console.log(flattened);
// [1, 2, 3, 4]
const nestedArray2 = [1, 2, [3, 4, [6, 7]]];
const flattened2 = nestedArray2.flat(1); //Solo queremos aplanar un nivel de profundidad
console.log(flattened2);
// [1, 2, 3, 4, [6,7]] // [6,7] queda intacto
Flatmap funciona de forma muy parecida a flat pero como su propio nombre indica, antes de aplanar el array aplica una función de mapeo a cada uno de los elementos del array y devuelve el resultado aplanado:
const myArray = [1, 2, 3, 4];
myArray.map((x) => [x * 2]);
// [[2], [4], [6], [8]]
nestedArray.flatMap((x) => [x * 2]);
// [2, 4, 6, 8]
El nombre de esta funcionalidad es bastante parecido a Promise.all que nos devuelve una promesa cuando todas las promesas pasadas han sido ejecutadas con éxito, o al menos, una de ellas ha fallado:
const p1 = new Promise((resolve) => resolve("Promise one"));
const p2 = new Promise((_, reject) => reject("Promise rejected!"));
const p3 = new Promise((resolve) => resolve("Promise two"));
Promise.all([p1, p2, p3])
.then((res) => console.log(res))
.catch((err) => console.log(err));
// Promise rejected!
Esto impone una limitación y es que en el momento que haya una sola promesa rejected el resto no se ejecutara y esto es precisamente lo que podemos evitar con Promise.allSettled que nos devolverá una promesa con el status final de todas y cada una de las promesas y su correspondiente valor de retorno aunque una o mas fallen:
const p1 = new Promise((resolve) => resolve("Promise one"));
const p2 = new Promise((_, reject) => reject("Promise rejected!"));
const p3 = new Promise((resolve) => resolve("Promise two"));
Promise.allSettled([p1, p2, p3])
.then((res) => console.log(res))
.catch((err) => console.log(err));
/*
0: {status: "fulfilled", value: "Promise one"}
1: {status: "rejected", reason: "Promise rejected!"}
2: {status: "fulfilled", value: "Promise two"}
*/
El método match se utiliza para obtener todas las ocurrencias de una expresión regular en una cadena. MatchAll hace lo mismo pero en lugar de devolvernos un Array, nos devuelve un Iterador cuyos valores podemos obtener mediante el método next que tienes todos los iteradores o iterarando el resultado mediante el bucle for of:
const str = "hello-hello-hello!"; const regex = /hello/g; const matches = str.matchAll(regex); //Podemos llamar al método next del iterador console.log(matches.next().value); // ["hello", index: 0, input: "hello-hello-hello!", groups: undefined] // O usar un bucle for of for (const match of matches) { console.log(match); } // ["hello", index: 6, input: "hello-hello-hello!", groups: undefined] // ["hello", index: 12, input: "hello-hello-hello!", groups: undefined
JavaScript tiene algunas limitaciones con respecto a los números, siendo 9007199254740991 (2 elevado a 53), el numero más grande posible que podíamos manejar en JavaScript con el tipo primitivo Number. Esto supone algunos problemas que afectan incluso al manejo de ciertas APIS cuando los ids numéricos son muy grandes como ha sido el caso de los ids usados por Twitter, y cuyo valor superaba el valor de Number.MAX_SAFE_INTEGER, obligando a tener que tratarlos como strings. Con BigInt esa limitación queda atrás y podemos crear números enteros más grandes añadiendo una "n" al final del entero o mediante la función BigInt:
const bigint = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// 9007199254740996n
console.log(bigint > Number.MAX_SAFE_INTEGER);
// true
Este operador (??) es un operador lógico similar al operador OR con la diferencia de que el operador "??" solo tiene en cuenta los valores undefined o nulos, pero no los false como sucede con el operador ||:
console.log(undefined || "default!"); // default!
console.log(null || "default!"); // default!
console.log(0 || "default!"); // default!
console.log(false || "default!"); // default
console.log(undefined ?? "default!"); // default!
console.log(null ?? "default!"); // default!
console.log(0 ?? "default!"); // 0
console.log(false ?? "default!"); // false
Este operador resulta muy útil cuando es usado con el operador de optional chaining que hemos visto anteriormente:
const departmentName = employee.department?.name ?? "no department!";
El acceso al objeto global (this) en JavaScript siempre ha dependido del entorno, usando window o self en el browser, o global en NodeJs pero no había una forma estandarizada de acceder al objeto global hasta la llegada de globalthis:
console.log(typeof globalThis); // Object console.log(globalThis); // Window {parent: Window, opener: null, top: Window, length: 2, frames: Window, …}
Un problema, con el que todos hemos tenido que lidiar alguna vez es que cuando accedemos a las propiedades de un objeto usando Object.keys, el bucle for in o JSON.stringify el orden de las propiedades no está garantizado sencillamente porque los objetos son una collection no ordenada de propiedades.
const myObject = {
a: 1,
b: 2,
c: 3,
};
Object.keys(myObject); // ["a", "b", "c"] orden NO garantizado!
for (let k in myObject) {
console.log(k);
} // a, b, c orden NO garantizado!
Con la inclusión de esta for-in-order, podemos decir adiós a estos problemas cuando iteremos las propiedades de los objetos.
Pues estas son, todas las funcionalidades que en el momento de escribir este post, ya son propuesas finales e incluídas en el stándar ECMAScript 2020 pero es posible que se incluyan algunas más, que actualmente están en stage 3 antes de cerrar la específica final. Estaremos atentos :)