ECMAScript 2026 incorpora mejoras para Maps, iteradores, errores, bytes, operaciones numéricas, fuentes asíncronas y JSON sin pérdida de precisión. Varias ya funcionan en runtimes actuales, pero su soporte todavía no es uniforme.
La edición 17 de ECMA-262 no trae una sintaxis que cambie por completo la forma de escribir JavaScript. En su lugar, estandariza siete soluciones pequeñas para problemas que llevamos años resolviendo con helpers, librerías o código repetido.
ECMAScript 2026 es la edición 17 del estándar del lenguaje. Las incorporaciones oficiales son:
MapyWeakMapcongetOrInsert.- Acceso al texto original de
JSON.parseyJSON.rawJSON. Iterator.concat.- Base64 y hexadecimal nativos en
Uint8Array. Math.sumPrecise.Error.isError.Array.fromAsync.
En este artículo veremos qué problema resuelve cada API, cómo se utiliza y qué soporte tiene realmente.
Antes de empezar: ES2026 no es lo mismo que “todo lo nuevo de JavaScript”
Algunas listas publicadas en internet mezclan funcionalidades de diferentes ediciones.
Estas APIs pertenecen a ECMAScript 2025, no a 2026:
RegExp.escape.Promise.try.- Iterator Helpers.
- Nuevos métodos de
Set. - Import Attributes y JSON Modules.
Tampoco forman parte de ES2026:
- Temporal.
- Explicit Resource Management y la sintaxis
using.
Ambas propuestas alcanzaron Stage 4 después del corte de la edición y están previstas para ECMAScript 2027.
1. Map.getOrInsert: obtener un valor o crearlo
Agrupar elementos en un Map suele requerir dos búsquedas:
const groups = new Map();
for (const item of items) {
if (!groups.has(item.category)) {
groups.set(item.category, []);
}
groups.get(item.category).push(item);
}ECMAScript 2026 añade:
Map.prototype.getOrInsert
Map.prototype.getOrInsertComputed
WeakMap.prototype.getOrInsert
WeakMap.prototype.getOrInsertComputedEl ejemplo anterior se reduce a:
const groups = new Map();
for (const item of items) {
groups
.getOrInsert(item.category, [])
.push(item);
}Si la clave existe, devuelve el valor actual. Si no existe, inserta el valor predeterminado y lo devuelve.
Calcular el valor únicamente cuando hace falta
No conviene construir un objeto costoso si la clave ya existe:
const cache = new Map();
const profile = cache.getOrInsertComputed(
userId,
key => loadProfile(key)
);El callback de getOrInsertComputed solo se ejecuta cuando la clave todavía no está en el Map.
También recibe la clave como argumento:
const groups = new Map();
const frontend = groups.getOrInsertComputed(
'frontend',
key => [key]
);
console.log(frontend);
// ['frontend']Esta API se parece a computeIfAbsent de Java, GetOrAdd de C# y setdefault de Python.
2. Array.fromAsync: convertir una fuente asíncrona en un array
Hasta ahora, convertir un async iterable requería acumular manualmente sus valores:
const values = [];
for await (const value of asyncSource) {
values.push(value);
}Con ES2026:
const values = await Array.fromAsync(asyncSource);También acepta una función de transformación:
async function* pages() {
yield 1;
yield Promise.resolve(2);
}
const values = await Array.fromAsync(
pages(),
value => value * 10
);
console.log(values);
// [10, 20]No es lo mismo que Promise.all
Array.fromAsync consume la fuente de forma progresiva y espera sus valores en secuencia, siguiendo el comportamiento de for await.
const sequential = await Array.fromAsync(promises);Promise.all primero recorre toda la fuente y espera los resultados en paralelo:
const parallel = await Promise.all(
Array.from(promises)
);La opción correcta depende de si necesitas lazy iteration, control de memoria o paralelismo.
3. Iterator.concat: unir iterables sin crear arrays intermedios
Para secuenciar varios iterables solíamos escribir un generator:
function* concat(...iterables) {
for (const iterable of iterables) {
yield* iterable;
}
}ECMAScript 2026 añade Iterator.concat:
const values = Iterator.concat(
[1, 2],
new Set([3, 4]),
[5]
);
console.log([...values]);
// [1, 2, 3, 4, 5]La operación es lazy. No crea un array con todos los elementos al llamar al método.
Esto resulta útil para:
- Combinar resultados paginados.
- Encadenar varias fuentes.
- Añadir prefijos o sufijos a un iterator.
- Procesar secuencias grandes sin materializarlas completas.
const records = Iterator.concat(
cachedRecords,
databaseRecords,
generatedRecords
);
for (const record of records) {
process(record);
}4. Base64 y hexadecimal directamente en Uint8Array
JavaScript tenía atob y btoa, pero esas APIs trabajan con strings binarios, no con arrays de bytes.
ES2026 añade:
Uint8Array.fromBase64()
Uint8Array.fromHex()
Uint8Array.prototype.toBase64()
Uint8Array.prototype.toHex()
Uint8Array.prototype.setFromBase64()
Uint8Array.prototype.setFromHex()Codificar bytes
const bytes = new TextEncoder().encode('Hello');
console.log(bytes.toBase64());
// SGVsbG8=
console.log(bytes.toHex());
// 48656c6c6fDecodificar Base64
const bytes = Uint8Array.fromBase64('SGVsbG8=');
const text = new TextDecoder().decode(bytes);
console.log(text);
// HelloBase64 URL-safe
const token = bytes.toBase64({
alphabet: 'base64url',
omitPadding: true,
});Esta variante es común en JWT, URLs y tokens.
Escribir sobre un buffer existente
setFromBase64 evita crear otro Uint8Array:
const target = new Uint8Array(8);
const result = target.setFromBase64('Zm9vYmFy');
console.log(result);
// { read: 8, written: 6 }5. Error.isError: detectar errores reales
La comprobación habitual es:
value instanceof ErrorEl problema aparece cuando el error proviene de otro realm, como un iframe, un worker o un contexto creado con node:vm.
const iframe = document.createElement('iframe');
document.body.append(iframe);
const externalError =
new iframe.contentWindow.Error('boom');
console.log(externalError instanceof Error);
// falseES2026 añade una comprobación equivalente a Array.isArray:
console.log(Error.isError(externalError));
// trueTambién evita depender de:
Object.prototype.toString.call(value)Esa técnica puede ser engañada mediante Symbol.toStringTag.
Error.isError resulta útil en:
- Sistemas centralizados de logging.
- Serialización de excepciones.
- Aplicaciones con iframes o workers.
- Código que recibe cualquier valor mediante
throw.
try {
await execute();
} catch (value) {
if (Error.isError(value)) {
logger.error(value.message, value.stack);
} else {
logger.error('Unknown thrown value', value);
}
}6. Math.sumPrecise: sumar números con menos error acumulado
La suma secuencial de punto flotante puede perder valores pequeños:
const values = [1e20, 0.1, -1e20];
const total = values.reduce(
(sum, value) => sum + value,
0
);
console.log(total);
// 0El resultado matemático es 0.1, pero el valor se pierde por el orden de las operaciones.
ECMAScript 2026 añade:
const total = Math.sumPrecise([
1e20,
0.1,
-1e20,
]);
console.log(total);
// 0.1El método recibe un iterable, no argumentos separados:
Math.sumPrecise(values);Esto evita los límites de stack que tendría una API variádica con miles de elementos.
Lo que no resuelve
Math.sumPrecise sigue trabajando con valores number.
No convierte JavaScript en un sistema decimal exacto y no debería utilizarse como sustituto de una librería decimal para cálculos financieros.
Tampoco acepta BigInt.
Su objetivo es devolver la suma correctamente redondeada que obtendríamos al sumar con precisión arbitraria y convertir el resultado nuevamente a punto flotante.
7. JSON sin pérdida de precisión
Una API puede devolver un entero perfectamente válido que no cabe dentro del rango seguro de JavaScript:
{
"user_id": 9223372036854775807
}El JSON conserva todos los dígitos, pero JSON.parse convierte el valor a number:
const payload =
'{"user_id":9223372036854775807}';
const result = JSON.parse(payload);
console.log(result.user_id);
// 9223372036854776000Convertir ese resultado a BigInt no recupera la información perdida.
Acceder al texto original
El reviver de JSON.parse recibe ahora un tercer argumento:
const result = JSON.parse(
payload,
(key, value, context) => {
if (key === 'user_id') {
return BigInt(context.source);
}
return value;
}
);
console.log(result.user_id);
// 9223372036854775807ncontext.source contiene el fragmento exacto de JSON que produjo el valor primitivo.
Un parser reutilizable
export function parseLosslessJson(text) {
return JSON.parse(
text,
(_key, value, context) => {
const isUnsafeInteger =
typeof value === 'number' &&
!Number.isSafeInteger(value) &&
/^-?\d+$/.test(context.source);
return isUnsafeInteger
? BigInt(context.source)
: value;
}
);
}Los enteros seguros y los decimales permanecen como number:
const result = parseLosslessJson(
'{"user_id":9223372036854775807,"count":42,"ratio":0.125}'
);
console.log(result);
// {
// user_id: 9223372036854775807n,
// count: 42,
// ratio: 0.125
// }Serializar con JSON.rawJSON
JSON.stringify no acepta BigInt directamente:
JSON.stringify({
user_id: 9223372036854775807n
});
// TypeErrorJSON.rawJSON permite insertar texto JSON validado:
const encoded = JSON.stringify({
user_id: JSON.rawJSON(
'9223372036854775807'
)
});
console.log(encoded);
// {"user_id":9223372036854775807}Un serializer genérico:
export function stringifyLosslessJson(value) {
return JSON.stringify(
value,
(_key, current) =>
typeof current === 'bigint'
? JSON.rawJSON(current.toString())
: current
);
}Si controlas el contrato de la API, los identificadores deberían seguir enviándose como strings. Estas nuevas APIs son especialmente útiles cuando consumes un contrato externo que no puedes modificar.
Soporte real en runtimes actuales
Que una API aparezca en la especificación no significa que esté disponible en todos los entornos.
Estas comprobaciones fueron realizadas directamente sobre los runtimes indicados el 26 de julio de 2026:
| API | Node 22.23 | Node 24.18 | Node 26.5 | Chromium 150 |
|---|---|---|---|---|
Array.fromAsync |
Sí | Sí | Sí | Sí |
Map.getOrInsert |
No | No | Sí | Sí |
Iterator.concat |
No | No | Sí | Sí |
Base64/hex en Uint8Array |
No | No | Sí | Sí |
Error.isError |
No | Sí | Sí | Sí |
Math.sumPrecise |
No | No | No | Sí |
| JSON source / raw JSON | Sí | Sí | Sí | Sí |
El caso de Math.sumPrecise demuestra por qué no conviene asumir soporte basándose únicamente en el número de versión de Node: incluso Node 26 todavía no lo implementa.
Feature detection
Para código que se ejecuta en diferentes runtimes:
const support = {
arrayFromAsync:
typeof Array.fromAsync === 'function',
mapGetOrInsert:
typeof Map.prototype.getOrInsert ===
'function',
iteratorConcat:
typeof Iterator !== 'undefined' &&
typeof Iterator.concat === 'function',
uint8ArrayBase64:
typeof Uint8Array.prototype.toBase64 ===
'function',
mathSumPrecise:
typeof Math.sumPrecise === 'function',
errorIsError:
typeof Error.isError === 'function',
rawJson:
typeof JSON.rawJSON === 'function',
};No uses un fallback que cambie silenciosamente el resultado. Para datos críticos, falla de forma explícita o carga un polyfill probado.
Cuáles usaría hoy
En proyectos que controlan su versión mínima de runtime:
Map.getOrInsertelimina bastante código repetitivo.- Las conversiones de
Uint8Arrayreemplazan helpers de Base64 y hex. Error.isErrores una mejora directa para logging.Array.fromAsyncsimplifica tests y consumo de streams.- Las APIs de JSON resuelven un problema de integridad de datos.
Usaría Iterator.concat cuando el beneficio de lazy evaluation sea real. Para arrays pequeños, el spread sigue siendo más familiar:
const combined = [...first, ...second];Esperaría antes de depender de Math.sumPrecise en código multiplataforma. Su soporte todavía es menor que el del resto de la edición.
Conclusión
ECMAScript 2026 es una edición orientada a eliminar pequeños puntos de fricción.
No cambia la sintaxis central del lenguaje, pero sí reduce la necesidad de mantener utilidades propias para:
- Inicializar valores en Maps.
- Consumir iterables asíncronos.
- Secuenciar iterators.
- Codificar bytes.
- Detectar errores entre realms.
- Sumar valores con menos pérdida.
- Preservar números grandes al procesar JSON.
La parte más importante no es memorizar siete nombres nuevos. Es reconocer cuál de estas APIs sustituye código real en tu proyecto y comprobar si el runtime donde despliegas ya la implementa.