12k
All articles

Novedades de ECMAScript 2026

ES2026 añade siete funciones de JavaScript: Array.fromAsync, Math.sumPrecise, métodos base64 y hex de Uint8Array, acceso de origen en JSON.parse y getOrInsert.

OpenReplay Team
OpenReplay Team
Novedades de ECMAScript 2026

Ecma International aprobó la especificación del lenguaje ECMAScript 2026 el 30 de junio de 2026, y contiene exactamente siete características nuevas: Array.fromAsync, Error.isError, Math.sumPrecise, métodos base64 y hexadecimal en Uint8Array, Iterator.concat, acceso al texto fuente en JSON.parse y getOrInsert en Map y WeakMap.

Cada año estos métodos aterrizan discretamente, y la mayoría de nosotros los descubrimos el día en que eliminamos un helper que llevábamos arrastrando durante años. Esta vez probablemente sea una utilidad de base64.

Si has buscado información sobre ES2026 recientemente, es probable que hayas encontrado listas que se contradicen entre sí, varias de las cuales incluyen Temporal o la palabra clave using. Esas listas están equivocadas, y este artículo explica por qué; luego recorre cada una de las siete características que realmente se incorporaron: el problema que resuelve, el código que sustituye y la línea única que lo reemplaza.

El registro autorizado es el anuncio de aprobación de Ecma, que confirma ES2026 como la 17.ª edición de ECMA-262, y la tabla de propuestas finalizadas de TC39, donde exactamente siete filas tienen un año de publicación previsto de 2026.

Puntos clave

  • ES2026 se aprobó el 30 de junio de 2026 y añade exactamente siete características; Temporal y Explicit Resource Management no están entre ellas.
  • Math.sumPrecise([1e17, 1, -1e17]) devuelve 1, mientras que los mismos valores sumados con reduce devuelven 0.
  • Uint8Array incorpora los métodos nativos toBase64(), toHex(), fromBase64() y fromHex(), lo que elimina una dependencia habitual.
  • El reviver de JSON.parse ahora recibe un tercer argumento que expone el texto fuente sin procesar, y JSON.rawJSON() resuelve el lado del stringify, de modo que los BigInt pueden completar un ciclo de ida y vuelta a través de JSON.
  • Map.prototype.getOrInsert(key, value) devuelve el valor existente cuando la clave ya está presente; solo inserta tu valor predeterminado cuando la clave no existe.

¿Qué no entró en ES2026?

Temporal y Explicit Resource Management (using / await using) no forman parte de ES2026. En la lista de propuestas finalizadas de TC39, ambas tienen un año de publicación previsto de 2027, junto con Atomics.pause y Joint Iteration. Las cuatro alcanzaron la etapa 4, el paso final del proceso de TC39, después de la fecha límite de ES2026. La cobertura previa a la aprobación de finales de 2025 predijo ampliamente que llegarían en 2026, de ahí provienen las listas de características contradictorias. La comprobación que cualquiera puede hacer: filtrar la columna del año de publicación previsto en la tabla de propuestas finalizadas. Siete propuestas indican 2026, y ninguna de ellas es Temporal.

Array.fromAsync

Array.fromAsync toma un iterador asíncrono y devuelve un array, envuelto en una promesa. También acepta un iterador sincrónico cuyos valores son promesas, un objeto array-like y una función de mapeo opcional, igual que hace Array.from. Hasta ahora, recopilar un iterador asíncrono implicaba un bucle for await insertando elementos en un array mutable:

const arr = [];
for await (const n of asyncGen(1, 3)) {
  arr.push(`page ${n}`);
}

El mismo resultado se obtiene ahora con una sola expresión:

const arr = await Array.fromAsync(asyncGen(1, 3), (n) => `page ${n}`);
// [ 'page 1', 'page 2', 'page 3' ]

Error.isError

Error.isError(value) devuelve true únicamente cuando el valor capturado es un objeto Error genuino. JavaScript te permite lanzar cualquier cosa, e instanceof Error resulta engañoso en cuanto los valores cruzan los límites de un iframe o de un realm, porque cada realm tiene su propio constructor Error. Un bloque catch condicionado a instanceof Error puede omitir silenciosamente la rama que muestra un mensaje útil, y las repeticiones de sesión de esa clase de error muestran un patrón familiar: el usuario atascado en una pantalla que no informa de nada mientras por debajo se ha producido un throw real. Error.isError cierra esa brecha.

try {
  throw new Error("session upload failed");
} catch (error) {
  Error.isError(error); // true
}

try {
  throw "session upload failed";
} catch (error) {
  Error.isError(error); // false
}

Math.sumPrecise

Math.sumPrecise suma un iterable de números sin acumular error de punto flotante en los resultados intermedios. Una cadena de reduce suma por pares, por lo que las magnitudes grandes y pequeñas se cancelan de forma desastrosa:

const values = [1e17, 1, -1e17];

values.reduce((a, b) => a + b, 0); // 0
Math.sumPrecise(values);           // 1

La versión con reduce pierde el 1 por completo. Más allá de la precisión, esto también jubila el reducer de suma como código repetitivo.

toBase64, toHex, fromBase64 y fromHex en Uint8Array

ES2026 dota a Uint8Array de los métodos nativos toBase64() y toHex(), además de los estáticos Uint8Array.fromBase64() y Uint8Array.fromHex() para la decodificación. La conversión de binario a texto antes implicaba incorporar una librería o implementar la codificación a mano; esta es una dependencia que la mayoría de los proyectos ya puede eliminar.

const value = new Uint8Array([79, 112, 101, 110, 82, 101, 112, 108, 97, 121]);

const valueBase64 = value.toBase64(); // 'T3BlblJlcGxheQ=='
const decoded = Uint8Array.fromBase64(valueBase64);

new TextDecoder().decode(decoded); // 'OpenReplay'

Iterator.concat

Iterator.concat() encadena iteradores en secuencia. Hasta ahora, la única forma de hacerlo era un generador escrito a mano que recorriera cada fuente y delegara con yield*:

function* combine(...iterators) {
  for (const source of iterators) {
    yield* source;
  }
}

Ahora es una sola llamada, y los arrays simples de valores pueden intercalarse entre iteradores. El [300] que aparece en el medio a continuación es un array simple y no necesita envolverse en Iterator.from.

const iOne = Iterator.from([100, 200]);
const iTwo = Iterator.from([400, 500]);

const combined = Iterator.concat(iOne, [300], iTwo);
Array.from(combined); // [ 100, 200, 300, 400, 500 ]

Acceso al texto fuente en JSON.parse

Antes de ES2026, el ciclo de ida y vuelta de JSON en JavaScript perdía información en ambas direcciones. Parsear un literal entero grande lo redondea silenciosamente, y convertir un BigInt a cadena lanza un error:

JSON.parse("9007199254740993");   // 9007199254740992
JSON.stringify(9007199254740993n); // TypeError: Do not know how to serialize a BigInt

La propuesta de acceso al texto fuente en JSON.parse resuelve ambos lados. JSON.parse ahora pasa a su reviver un tercer argumento que contiene el texto source intacto, que puedes convertir tú mismo sin perder dígitos. En el lado del stringify, JSON.rawJSON() permite que un replacer emita un valor de forma literal:

JSON.parse("9007199254740993", (key, value, { source }) => BigInt(source));
// 9007199254740993n

JSON.stringify(9007199254740993n, (key, value) => JSON.rawJSON(value));
// 9007199254740993

Un BigInt ahora puede sobrevivir a un ciclo de ida y vuelta por JSON sin perder dígitos ni lanzar errores.

Map.prototype.getOrInsert y WeakMap.prototype.getOrInsert

La propuesta Upsert trae getOrInsert(key, value) tanto a Map como a WeakMap, de modo que ya no necesitas llamar a has() y luego a set(). La semántica importa: getOrInsert devuelve el valor existente cuando la clave ya está presente. Solo inserta, y solo devuelve tu valor predeterminado, cuando la clave no existe.

const limits = new Map();
limits.set("uploads", 25);

limits.getOrInsert("exports", 10);   // 10  (inserted)
limits.getOrInsert("uploads", 100);  // 25  (existing value wins)

Observa que 100 se descarta porque "uploads" ya estaba definido. Trata el segundo argumento como un valor predeterminado, no como una sobrescritura.

¿Qué puedes eliminar ahora?

ES2026 es una versión de limpieza: cada una de las siete características jubila un patrón que probablemente tengas hoy en producción. La dependencia del helper de base64, el reducer de suma, los bloques de guarda con has() seguido de set(), el generador de concatenación con yield* y el bucle de recopilación con for await tienen todos reemplazos nativos directos. La disponibilidad en tiempo de ejecución varía según el motor, así que consulta la referencia de objetos globales estándar de MDN para conocer el estado actual de soporte de cada método antes de eliminar polyfills. Después, empieza por la victoria más sencilla en tu base de código, que para la mayoría de los equipos consiste en sustituir las comprobaciones de existencia en Map por getOrInsert.

Preguntas frecuentes

¿Cuál es la diferencia entre Array.fromAsync y Promise.all?

Array.fromAsync espera los valores de forma secuencial, uno a uno, y acepta iterables asíncronos, iterables sincrónicos que producen promesas y objetos array-like. Promise.all ejecuta un iterable de promesas de forma concurrente y se rechaza en cuanto cualquiera de las promesas se rechaza. Usa Promise.all para trabajo en paralelo y Array.fromAsync cuando el orden importa o la fuente es un iterador asíncrono. Ambos devuelven una promesa que se resuelve en un array.

¿Error.isError devuelve true para TypeError y otras subclases de Error?

Sí. Error.isError devuelve true para todas las subclases genuinas de Error, incluidas TypeError, RangeError, SyntaxError y clases personalizadas que extienden Error, porque los motores comprueban una marca interna que llevan los errores reales en lugar de la cadena de prototipos. En los navegadores, un DOMException también cuenta como un error real. Devuelve false para objetos que simplemente imitan errores, como objetos simples con una propiedad message u objetos creados con Object.create(Error.prototype).

¿Puede Uint8Array.prototype.toBase64 producir base64 apto para URL?

Sí. toBase64 acepta un objeto de opciones con una propiedad alphabet establecida en 'base64' (el valor predeterminado, que usa + y /) o 'base64url' (que usa - y _), además de un booleano omitPadding que elimina los caracteres = finales. Uint8Array.fromBase64 acepta la misma opción alphabet y una opción lastChunkHandling ('loose', 'strict' o 'stop-before-partial') que controla cómo se decodifica un bloque final de menos de cuatro caracteres.

¿Cómo evito calcular un valor predeterminado costoso con getOrInsert?

Usa getOrInsertComputed(key, callback), que la misma propuesta Upsert añade tanto a Map como a WeakMap. Llama al callback para producir el valor solo cuando la clave no existe, y luego inserta y devuelve el resultado; cuando la clave ya existe, el callback nunca se ejecuta y se devuelve el valor existente. El getOrInsert normal siempre evalúa su segundo argumento antes de la llamada, incluso cuando la clave ya está presente.

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.