Volver al blog

ECMAScript 2025: Los Nuevos Recursos de JavaScript Que Necesitas Conocer

Hola HaWkers, un año más y una actualización más del ECMAScript fue oficialmente aprobada por el TC39. El ECMAScript 2025 trae recursos que pueden parecer incrementales a primera vista, pero que resuelven problemas reales que desarrolladores enfrentan en el día a día.

¿Ya tuviste dificultades con regex complejas o necesitaste optimizar memoria en aplicaciones gráficas? Las novedades de este año abordan exactamente esos escenarios.

Qué Cambió en ECMAScript 2025

El proceso de evolución de JavaScript sigue un ciclo anual bien definido. Propuestas pasan por etapas (Stage 0 a Stage 4) antes de ser incorporadas al estándar oficial. En 2025, tenemos algunas adiciones interesantes que ya están disponibles en los principales navegadores y runtimes.

Principales Adiciones

Recursos aprobados en ES2025:

  1. Float16Array - Nuevo TypedArray para precisión de 16 bits
  2. Duplicate Named Capture Groups - Regex más flexibles
  3. Set Methods - Operaciones de conjunto nativas
  4. Promise.try - Tratamiento unificado de errores
  5. RegExp.escape - Escape seguro de strings para regex
  6. Import Attributes - Metadatos en imports

Float16Array: Optimización de Memoria Para Gráficos e IA

El Float16Array es una de las adiciones más significativas para quien trabaja con WebGL, WebGPU o machine learning en el browser.

Por Qué Float16 Importa

Hasta ahora, teníamos apenas Float32Array y Float64Array. ¿El problema? Muchas aplicaciones gráficas y de IA no necesitan tanta precisión, y usar 32 o 64 bits desperdiciaba memoria.

// Comparación de tamaño en bytes para 1000 elementos
const float64 = new Float64Array(1000); // 8000 bytes
const float32 = new Float32Array(1000); // 4000 bytes
const float16 = new Float16Array(1000); // 2000 bytes - ¡NUEVO!

console.log('Float64:', float64.byteLength, 'bytes');
console.log('Float32:', float32.byteLength, 'bytes');
console.log('Float16:', float16.byteLength, 'bytes');
// ¡Float16 usa 50% menos memoria que Float32!

Uso Práctico con WebGPU

// Creando buffer de vértices optimizado para WebGPU
const vertices = new Float16Array([
  // x, y, z, u, v - coordenadas y texturas
  -1.0, -1.0, 0.0, 0.0, 0.0,
   1.0, -1.0, 0.0, 1.0, 0.0,
   0.0,  1.0, 0.0, 0.5, 1.0,
]);

// Funciones auxiliares para conversión
const value = 3.14159;
const f16Bits = Math.f16round(value); // Redondea para Float16

// Verificar si el valor puede ser representado en Float16
function isFloat16Safe(num) {
  const rounded = Math.f16round(num);
  return Object.is(num, rounded) ||
         (Number.isNaN(num) && Number.isNaN(rounded));
}

console.log(isFloat16Safe(0.1));     // true
console.log(isFloat16Safe(65536));   // false (overflow)

DataView con Float16

const buffer = new ArrayBuffer(10);
const view = new DataView(buffer);

// Nuevos métodos del DataView
view.setFloat16(0, 3.14, true);  // little-endian
view.setFloat16(2, 2.71, false); // big-endian

const pi = view.getFloat16(0, true);
const e = view.getFloat16(2, false);

console.log(pi); // ~3.140625 (precisión de 16 bits)
console.log(e);  // ~2.710938

Duplicate Named Capture Groups

Este recurso resuelve una limitación frustrante de las regex en JavaScript: no podíamos usar el mismo nombre en grupos de captura alternativos.

El Problema Anterior

// ANTES: ¡Esto generaba error de sintaxis!
// const dateRegex = /(?<year>\d{4})-(?<month>\d{2})|(?<month>\d{2})\/(?<year>\d{4})/;
// SyntaxError: Duplicate capture group name

// Teníamos que usar nombres diferentes
const dateRegexOld = /(?<year1>\d{4})-(?<month1>\d{2})|(?<month2>\d{2})\/(?<year2>\d{4})/;

La Solución en ES2025

// AHORA: ¡Funciona perfectamente!
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})|(?<month>\d{2})\/(?<year>\d{4})/;

// Parsing de diferentes formatos de fecha
const dates = [
  '2025-11-30',    // formato ISO
  '30/11/2025',    // formato europeo
];

dates.forEach(date => {
  const match = date.match(dateRegex);
  if (match) {
    console.log(`Año: ${match.groups.year}, Mes: ${match.groups.month}`);
  }
});
// Año: 2025, Mes: 11
// Año: 2025, Mes: 11

Caso de Uso Real: Parser de Logs

// Parser flexible para diferentes formatos de log
const logRegex = /
  (?:\[(?<level>INFO|WARN|ERROR)\])|
  (?:(?<level>info|warn|error):)
/xi;

const logs = [
  '[ERROR] Database connection failed',
  'warn: Memory usage high',
  '[INFO] Server started',
];

logs.forEach(log => {
  const match = log.match(logRegex);
  if (match) {
    const level = match.groups.level.toUpperCase();
    console.log(`Nivel: ${level}`);
  }
});

Set Methods: Operaciones de Conjunto Nativas

Finalmente tenemos métodos nativos para operaciones de conjunto que antes necesitábamos implementar manualmente.

Nuevos Métodos Disponibles

const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([4, 5, 6, 7, 8]);

// Union - elementos en A o B
const union = setA.union(setB);
console.log([...union]); // [1, 2, 3, 4, 5, 6, 7, 8]

// Intersection - elementos en A y B
const intersection = setA.intersection(setB);
console.log([...intersection]); // [4, 5]

// Difference - elementos en A pero no en B
const difference = setA.difference(setB);
console.log([...difference]); // [1, 2, 3]

// Symmetric Difference - elementos en A o B, pero no en ambos
const symmetricDiff = setA.symmetricDifference(setB);
console.log([...symmetricDiff]); // [1, 2, 3, 6, 7, 8]

Métodos de Verificación

const admins = new Set(['alice', 'bob']);
const users = new Set(['alice', 'bob', 'charlie', 'diana']);
const guests = new Set(['eve', 'frank']);

// isSubsetOf - ¿A está contenido en B?
console.log(admins.isSubsetOf(users));     // true
console.log(users.isSubsetOf(admins));     // false

// isSupersetOf - ¿A contiene B?
console.log(users.isSupersetOf(admins));   // true

// isDisjointFrom - ¿A y B no tienen elementos en común?
console.log(admins.isDisjointFrom(guests)); // true
console.log(admins.isDisjointFrom(users));  // false

Aplicación Práctica: Sistema de Permisos

class PermissionManager {
  constructor() {
    this.roles = {
      admin: new Set(['read', 'write', 'delete', 'manage_users']),
      editor: new Set(['read', 'write']),
      viewer: new Set(['read']),
    };
  }

  hasAllPermissions(userPermissions, requiredPermissions) {
    return requiredPermissions.isSubsetOf(userPermissions);
  }

  getMissingPermissions(userPermissions, requiredPermissions) {
    return requiredPermissions.difference(userPermissions);
  }

  combineRoles(...roleNames) {
    return roleNames.reduce((combined, role) => {
      return combined.union(this.roles[role] || new Set());
    }, new Set());
  }
}

const pm = new PermissionManager();
const userPerms = pm.combineRoles('editor', 'viewer');
const required = new Set(['read', 'write', 'delete']);

console.log('Has all:', pm.hasAllPermissions(userPerms, required)); // false
console.log('Missing:', [...pm.getMissingPermissions(userPerms, required)]); // ['delete']

Promise.try: Tratamiento Unificado de Errores

El Promise.try resuelve un problema común: cuando tienes una función que puede ser síncrona o asíncrona y quieres tratar errores de forma unificada.

El Problema

// ANTES: Código inconsistente
function processData(data) {
  // ¡Si esto lanza error síncrono, el catch no lo atrapa!
  return validateSync(data)
    .then(validated => transform(validated))
    .catch(err => console.error(err));
}

// Solución antigua (verbosa)
function processDataOld(data) {
  return new Promise(resolve => resolve(validateSync(data)))
    .then(validated => transform(validated))
    .catch(err => console.error(err));
}

La Solución con Promise.try

// AHORA: Limpio y consistente
function processData(data) {
  return Promise.try(() => validateSync(data))
    .then(validated => transform(validated))
    .catch(err => console.error(err));
}

// Funciona con funciones sync y async
const result1 = Promise.try(() => {
  return 42; // síncrono
});

const result2 = Promise.try(async () => {
  const response = await fetch('/api/data');
  return response.json(); // asíncrono
});

// Errores síncronos son capturados
const result3 = Promise.try(() => {
  throw new Error('Sync error');
}).catch(err => {
  console.log('Caught:', err.message); // Caught: Sync error
});

RegExp.escape: Escape Seguro de Strings

Cuando necesitas usar input del usuario en una regex, escapar caracteres especiales era manual y propenso a errores.

// ANTES: Función manual (común en proyectos)
function escapeRegExpOld(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// AHORA: Método nativo
const userInput = 'Hello (World)? [Test]';
const escaped = RegExp.escape(userInput);
console.log(escaped); // Hello \(World\)\? \[Test\]

// Uso seguro en regex dinámica
function highlightText(text, searchTerm) {
  const safePattern = RegExp.escape(searchTerm);
  const regex = new RegExp(`(${safePattern})`, 'gi');
  return text.replace(regex, '<mark>$1</mark>');
}

const result = highlightText(
  'Price is $50 (special offer!)',
  '$50 (special'
);
// Price is <mark>$50 (special</mark> offer!)

Import Attributes: Metadatos en Imports

Import Attributes permiten pasar informaciones adicionales al importar módulos, especialmente útil para JSON y otros tipos de archivos.

// Importando JSON con type assertion
import config from './config.json' with { type: 'json' };

// Import dinámico con atributos
const data = await import('./data.json', {
  with: { type: 'json' }
});

// Esto ayuda engines a optimizar y validar imports
// También mejora la seguridad al explicitar el tipo esperado

Compatibilidad y Adopción

Soporte en los Navegadores

Recurso Chrome Firefox Safari Node.js
Float16Array 127+ 129+ 18.2+ 22+
Duplicate Named Groups 125+ 128+ 18+ 22+
Set Methods 122+ 127+ 17+ 22+
Promise.try 128+ En desarrollo 18+ 22+
RegExp.escape En desarrollo En desarrollo 18+ 22+

Usando Hoy con Transpiladores

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: { node: 'current' },
      shippedProposals: true,
    }],
  ],
};

Conclusión

El ECMAScript 2025 puede no tener el impacto visual de adiciones como async/await u optional chaining, pero cada recurso resuelve problemas prácticos. Float16Array va a beneficiar aplicaciones gráficas y de IA, los Set Methods simplifican código que antes era verboso, y Promise.try estandariza algo que desarrolladores implementaban de formas diferentes.

La evolución incremental de JavaScript muestra la madurez del lenguaje. No necesitamos revoluciones cada año; mejoras puntuales y bien pensadas mantienen el lenguaje moderno sin romper compatibilidad.

Si quieres acompañar más novedades sobre JavaScript y desarrollo web, te recomiendo echar un vistazo al artículo sobre Svelte 5 y Runes donde exploramos otra innovación que está transformando cómo escribimos código reactivo.

¡Vamos a por ello! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios