Volver al blog

ECMAScript 2025: Las Nuevas Features de JavaScript Que Necesitas Conocer

Hola HaWkers, ECMAScript 2025 trajo una de las actualizaciones más sustanciales de los últimos años para JavaScript. Con nuevos Iterator helpers, métodos de Set, importación directa de JSON y mejoras en Promises, el lenguaje continúa evolucionando para atender las necesidades de los desarrolladores modernos.

Vamos a explorar cada una de estas novedades con ejemplos prácticos que puedes comenzar a usar hoy mismo.

Iterator Helpers: La Gran Estrella

La adición más esperada del ES2025 son los Iterator helpers. Finalmente, podemos usar métodos como map, filter y reduce directamente en iteradores, sin necesitar convertir a arrays.

Por Qué Esto Importa

Antes, trabajar con iteradores exigía conversión constante:

// Antes del ES2025 - Ineficiente para grandes conjuntos
function* generarNumeros(limite) {
  for (let i = 0; i < limite; i++) {
    yield i;
  }
}

// Necesitaba convertir a array primero
const numeros = [...generarNumeros(1000000)];
const pares = numeros.filter(n => n % 2 === 0);
const doblados = pares.map(n => n * 2);
const suma = doblados.reduce((acc, n) => acc + n, 0);
// Problema: Crea 3 arrays intermediarios en memoria

Ahora con Iterator helpers:

// ES2025 - Lazy evaluation, sin arrays intermediarios
function* generarNumeros(limite) {
  for (let i = 0; i < limite; i++) {
    yield i;
  }
}

const resultado = generarNumeros(1000000)
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .reduce((acc, n) => acc + n, 0);

// Procesa item por item, sin crear arrays intermediarios
console.log(resultado);

Métodos Disponibles

El objeto Iterator ahora posee métodos funcionales nativos:

// map - Transforma cada elemento
const iterator = [1, 2, 3].values();
const doblados = iterator.map(x => x * 2);
console.log([...doblados]); // [2, 4, 6]

// filter - Filtra elementos
const numeros = [1, 2, 3, 4, 5].values();
const pares = numeros.filter(x => x % 2 === 0);
console.log([...pares]); // [2, 4]

// take - Toma los primeros N elementos
const infinito = function* () {
  let i = 0;
  while (true) yield i++;
}();
const primeros = infinito.take(5);
console.log([...primeros]); // [0, 1, 2, 3, 4]

// drop - Salta los primeros N elementos
const lista = [1, 2, 3, 4, 5].values();
const sinPrimeros = lista.drop(2);
console.log([...sinPrimeros]); // [3, 4, 5]

// flatMap - Map + flatten
const anidado = [[1, 2], [3, 4]].values();
const aplanado = anidado.flatMap(arr => arr.values());
console.log([...aplanado]); // [1, 2, 3, 4]

Caso de Uso Real: Procesamiento de Datos

// Procesando líneas de un archivo grande con lazy evaluation
async function* leerLineas(archivo) {
  const reader = archivo.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lineas = buffer.split('\n');
    buffer = lineas.pop(); // Guarda línea incompleta

    for (const linea of lineas) {
      yield linea;
    }
  }

  if (buffer) yield buffer;
}

// Usando iterator helpers para procesar
const csvProcesado = leerLineas(archivoGrande)
  .filter(linea => linea.trim() !== '')
  .filter(linea => !linea.startsWith('#'))
  .map(linea => linea.split(','))
  .filter(campos => campos.length === 4)
  .map(([nombre, email, edad, ciudad]) => ({
    nombre: nombre.trim(),
    email: email.trim(),
    edad: parseInt(edad),
    ciudad: ciudad.trim()
  }))
  .filter(persona => persona.edad >= 18);

// Solo 100 registros, sin cargar archivo entero
const primeros100 = csvProcesado.take(100);

Nuevos Métodos de Set

Los Sets JavaScript finalmente recibieron métodos que ya existían en otros lenguajes hace décadas.

Operaciones de Conjunto

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

// union - Todos los elementos de ambos conjuntos
const union = setA.union(setB);
console.log([...union]); // [1, 2, 3, 4, 5, 6, 7, 8]

// intersection - Elementos comunes
const interseccion = setA.intersection(setB);
console.log([...interseccion]); // [4, 5]

// difference - Elementos en A que no están en B
const diferencia = setA.difference(setB);
console.log([...diferencia]); // [1, 2, 3]

// symmetricDifference - Elementos exclusivos de cada conjunto
const diferenciaSimetrica = setA.symmetricDifference(setB);
console.log([...diferenciaSimetrica]); // [1, 2, 3, 6, 7, 8]

// isSubsetOf - Verifica si A está contenido en B
const subset = new Set([4, 5]);
console.log(subset.isSubsetOf(setA)); // true
console.log(subset.isSubsetOf(setB)); // true

// isSupersetOf - Verifica si A contiene B
console.log(setA.isSupersetOf(subset)); // true

// isDisjointFrom - Verifica si no hay elementos comunes
const setC = new Set([10, 11, 12]);
console.log(setA.isDisjointFrom(setC)); // true
console.log(setA.isDisjointFrom(setB)); // false

Aplicación Práctica: Sistema de Permisos

class SistemaPermisos {
  constructor() {
    this.roles = new Map();
  }

  definirRole(nombre, permisos) {
    this.roles.set(nombre, new Set(permisos));
  }

  permisosUsuario(rolesUsuario) {
    return rolesUsuario.reduce((acc, role) => {
      const permisosRole = this.roles.get(role) || new Set();
      return acc.union(permisosRole);
    }, new Set());
  }

  verificarAcceso(rolesUsuario, permisosNecesarios) {
    const permisosUsuario = this.permisosUsuario(rolesUsuario);
    const necesarios = new Set(permisosNecesarios);

    // Verifica si usuario tiene todos los permisos necesarios
    return necesarios.isSubsetOf(permisosUsuario);
  }

  permisosFaltantes(rolesUsuario, permisosNecesarios) {
    const permisosUsuario = this.permisosUsuario(rolesUsuario);
    const necesarios = new Set(permisosNecesarios);

    // Retorna permisos que el usuario no tiene
    return necesarios.difference(permisosUsuario);
  }
}

// Uso
const sistema = new SistemaPermisos();

sistema.definirRole('admin', ['crear', 'leer', 'actualizar', 'eliminar', 'gestionar_usuarios']);
sistema.definirRole('editor', ['crear', 'leer', 'actualizar']);
sistema.definirRole('lector', ['leer']);

const usuario = ['editor', 'lector'];
const permisosNecesarios = ['crear', 'eliminar'];

console.log(sistema.verificarAcceso(usuario, permisosNecesarios)); // false
console.log([...sistema.permisosFaltantes(usuario, permisosNecesarios)]); // ['eliminar']

Import JSON Directamente

Ahora podemos importar archivos JSON directamente como módulos ES, sin necesidad de fetch o require.

// config.json
// {
//   "apiUrl": "https://api.ejemplo.com",
//   "timeout": 5000,
//   "maxRetries": 3
// }

// Antes - Necesitaba fetch o fs
// const config = await fetch('./config.json').then(r => r.json());

// ES2025 - Import directo con assertion
import config from './config.json' with { type: 'json' };

console.log(config.apiUrl); // https://api.ejemplo.com
console.log(config.timeout); // 5000

Casos de Uso

// Importar datos estáticos
import traducciones from './lang/es-ES.json' with { type: 'json' };
import rutas from './routes.json' with { type: 'json' };
import schema from './validation-schema.json' with { type: 'json' };

// Dynamic import también funciona
const locale = 'en-US';
const mensajes = await import(`./lang/${locale}.json`, {
  with: { type: 'json' }
});

Promise.try: Simplificando Cadenas de Promises

El nuevo método Promise.try permite iniciar una cadena de Promises de forma más limpia, tratando tanto funciones síncronas como asíncronas.

// Antes - Necesitaba workarounds
function procesarDatos(datos) {
  // Puede lanzar excepción síncrona
  const validados = validarDatos(datos);

  // Puede retornar Promise
  return salvarEnBanco(validados);
}

// Workaround antiguo
Promise.resolve()
  .then(() => procesarDatos(datos))
  .then(resultado => console.log(resultado))
  .catch(error => console.error(error));

// ES2025 - Más limpio
Promise.try(() => procesarDatos(datos))
  .then(resultado => console.log(resultado))
  .catch(error => console.error(error));

Por Qué Promise.try Es Útil

// Función que puede ser síncrona o asíncrona
function buscarUsuario(id) {
  // Validación síncrona puede lanzar error
  if (!id || typeof id !== 'number') {
    throw new Error('ID inválido');
  }

  // Cache síncrono
  if (cache.has(id)) {
    return cache.get(id); // Retorno síncrono
  }

  // Búsqueda asíncrona
  return fetch(`/api/users/${id}`).then(r => r.json());
}

// Promise.try trata ambos casos uniformemente
Promise.try(() => buscarUsuario(userId))
  .then(usuario => renderizar(usuario))
  .catch(error => mostrarError(error));

// Equivalente a:
// new Promise(resolve => resolve(buscarUsuario(userId)))
//   .then(...)
//   .catch(...);

Float16Array: Nuevo Typed Array

Para aplicaciones de machine learning y procesamiento de datos que necesitan eficiencia de memoria, el nuevo Float16Array ofrece números de punto flotante de 16 bits.

// Float16Array usa mitad de la memoria de Float32Array
const datos16 = new Float16Array([1.5, 2.5, 3.5, 4.5]);
const datos32 = new Float32Array([1.5, 2.5, 3.5, 4.5]);

console.log(datos16.byteLength); // 8 bytes
console.log(datos32.byteLength); // 16 bytes

// Útil para ML donde precisión total no es necesaria
function crearTensorEficiente(dimensiones, valores) {
  const total = dimensiones.reduce((a, b) => a * b, 1);
  const datos = new Float16Array(total);

  for (let i = 0; i < valores.length; i++) {
    datos[i] = valores[i];
  }

  return {
    shape: dimensiones,
    data: datos,
    dtype: 'float16'
  };
}

const tensor = crearTensorEficiente([2, 3], [1, 2, 3, 4, 5, 6]);

Mejoras en Regular Expressions

ES2025 también trajo mejoras para expresiones regulares, incluyendo el modificador de flag inline.

// Modifier flag inline - Activa/desactiva flags en partes de la regex
const regex = /(?i:hello) world/;

// (?i:...) torna solo "hello" case-insensitive
console.log(regex.test('HELLO world')); // true
console.log(regex.test('hello WORLD')); // false (world necesita ser minúsculo)

// Útil para patrones complejos
const emailRegex = /(?i:[a-z0-9._%+-]+)@(?i:[a-z0-9.-]+)\.(?i:[a-z]{2,})/;

Soporte en los Navegadores

La mayoría de las features del ES2025 ya está disponible en los navegadores modernos:

Iterator Helpers:

  • Chrome 122+
  • Firefox 131+
  • Safari 17.4+

Set Methods:

  • Chrome 122+
  • Firefox 127+
  • Safari 17+

JSON Modules:

  • Chrome 123+
  • Firefox 135+
  • Safari 17.2+

Promise.try:

  • Chrome 128+
  • Firefox 132+
  • Safari 18+

Para proyectos que necesitan soportar navegadores más antiguos, utiliza transpiladores como Babel con los plugins apropiados.

Migrando Tu Código

Aquí está un checklist para comenzar a usar las nuevas features:

1. Actualiza tus herramientas:

npm update typescript @babel/core @babel/preset-env

2. Configura el target de TypeScript:

{
  "compilerOptions": {
    "target": "ES2025",
    "lib": ["ES2025", "DOM"]
  }
}

3. Agrega polyfills si es necesario:

npm install core-js@latest
// Importa solo los polyfills necesarios
import 'core-js/actual/iterator';
import 'core-js/actual/set';
import 'core-js/actual/promise/try';

ECMAScript 2025 representa un paso significativo en la evolución de JavaScript, trayendo features que tornan el código más expresivo y eficiente. Si quieres profundizar tus conocimientos en JavaScript moderno, recomiendo dar una mirada en el artículo sobre Web Workers: Performance con Threads donde aprenderás técnicas avanzadas de optimización.

¡Vamos a por ello! 🦅

Comentarios (0)

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

Añadir comentarios