ECMAScript 2024: evolucion pragmatica

La especificacion ECMAScript 2024 (ES15) fue aprobada oficialmente en junio de 2024 y trae funcionalidades que muchos desarrolladores llevaban pidiendo durante años. A diferencia de versiones anteriores que se centraban en sintaxis, ES2024 se enfoca en utilidades prácticas que simplifican el código del dia a dia.

Object.groupBy() y Map.groupBy()

Sin duda, la estrella de ES2024. Object.groupBy() permite agrupar elementos de un iterable usando una función clasificadora.

Antes vs. Ahora

// ANTES: agrupacion manual con reduce
const porCategoria = productos.reduce((acc, producto) => {
  const key = producto.categoria;
  if (!acc[key]) acc[key] = [];
  acc[key].push(producto);
  return acc;
}, {});

// AHORA: una sola linea
const porCategoria = Object.groupBy(productos, p => p.categoria);

Map.groupBy() para claves no-string

Cuando necesitas claves que no son strings (objetos, números, etc.), usa Map.groupBy():

const usuarios = [
  { nombre: 'Ana', edad: 25 },
  { nombre: 'Carlos', edad: 17 },
  { nombre: 'Diana', edad: 30 },
  { nombre: 'Luis', edad: 15 },
];

const porMayoriaEdad = Map.groupBy(usuarios, u =>
  u.edad >= 18 ? 'adulto' : 'menor'
);

// Map { 'adulto' => [...], 'menor' => [...] }

Consideraciones importantes

  • Object.groupBy() crea un objeto sin prototipo (Object.create(null))
  • Las claves se convierten a string (como en objetos normales)
  • Para claves complejas, usa Map.groupBy()
  • El iterable original no se modifica

Promise.withResolvers()

Este nuevo método estático simplifica un patrón muy comun: crear una Promise y extraer sus funciones resolve y reject.

// ANTES: patrón comun pero verboso
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// AHORA: limpio y directo
const { promise, resolve, reject } = Promise.withResolvers();

Caso de uso real: encapsular un EventEmitter

function esperarEvento(emitter, evento) {
  const { promise, resolve, reject } = Promise.withResolvers();

  emitter.addEventListener(evento, resolve, { once: true });
  emitter.addEventListener('error', reject, { once: true });

  return promise;
}

// Uso
const resultado = await esperarEvento(socket, 'message');

ArrayBuffer resizable y transferible

ES2024 introduce ArrayBuffer con tamaño ajustable y la capacidad de transferir ownership entre contextos.

ArrayBuffer resizable

// Crear un buffer que puede crecer hasta 1MB
const buffer = new ArrayBuffer(256, { maxByteLength: 1024 * 1024 });

console.log(buffer.byteLength); // 256
console.log(buffer.maxByteLength); // 1048576
console.log(buffer.resizable); // true

// Redimensionar
buffer.resize(512);
console.log(buffer.byteLength); // 512

Transferencia de ArrayBuffer

const original = new ArrayBuffer(1024);
const view = new Uint8Array(original);
view[0] = 42;

// Transferir a un nuevo buffer
const transferido = original.transfer();

console.log(original.byteLength); // 0 (desvinculado)
console.log(transferido.byteLength); // 1024
console.log(new Uint8Array(transferido)[0]); // 42

Esto es especialmente útil al comunicarse con Web Workers, eliminando la necesidad de copiar datos.

String.isWellFormed() y String.toWellFormed()

JavaScript permite strings con surrogate pairs incompletos, lo cual puede causar problemas al interactuar con APIs como encodeURIComponent(). Estos nuevos métodos resuelven eso.

const texto = 'Hola \uD800 mundo'; // surrogate suelto

console.log(texto.isWellFormed()); // false

const textoCorregido = texto.toWellFormed();
console.log(textoCorregido.isWellFormed()); // true
// Reemplaza surrogates sueltos con U+FFFD (caracter de reemplazo)

Cuando usarlos?

  • Antes de enviar strings a APIs externas
  • Al procesar datos de fuentes no confiables
  • Al usar TextEncoder o encodeURIComponent()

Atomics.waitAsync()

Una versión asincrona de Atomics.wait() que no bloquea el hilo principal:

const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);

// No bloquea el hilo principal
const resultado = Atomics.waitAsync(sharedArray, 0, 0);

if (resultado.async) {
  resultado.value.then(status => {
    console.log('Worker notificado:', status);
  });
}

// En otro Worker:
Atomics.store(sharedArray, 0, 1);
Atomics.notify(sharedArray, 0);

RegExp v flag (unicodeSets)

El nuevo flag v extiende las capacidades de las expresiones regulares con conjuntos de caracteres Unicode:

// Buscar emojis (con flag v)
const emojiRegex = /\p{RGI_Emoji}/v;
console.log(emojiRegex.test('hola')); // false

// Interseccion de conjuntos: letras griegas que sean mayusculas
const regex = /[\p{Script=Greek}&&\p{Lu}]/v;
console.log(regex.test('A')); // false (latina)

// Diferencia de conjuntos: letras excepto vocales
const consonantes = /[\p{Letter}--[aeiouAEIOU]]/v;

Operaciones de conjuntos con el flag v

Operacion Sintaxis Descripcion
Union [AB] A o B
Interseccion [A&&B] A y B
Diferencia [A--B] A pero no B

Tabla resumen de soporte

Funcionalidad Chrome Firefox Safari Node.js
Object.groupBy 117+ 119+ 17.4+ 21+
Promise.withResolvers 119+ 121+ 17.4+ 22+
ArrayBuffer.transfer 114+ 122+ 17.4+ 22+
String.isWellFormed 111+ 119+ 16.4+ 20+
Atomics.waitAsync 87+ 128+ 16.4+ 16+
RegExp v flag 112+ 116+ 17+ 20+

Conclusion

ES2024 trae mejoras que, aunque no son tan llamativas como los arrow functions o async/await, resuelven fricciones reales del desarrollo diario. Object.groupBy() y Promise.withResolvers() en particular van a cambiar la forma en que escribimos JavaScript. Lo mejor: ya tienen soporte amplio en todos los navegadores modernos. Es hora de adoptarlos.