JavaScript - ES9 (ECMAScript 2018) - Parte I
Temario Introducción Nuevas Características Rest y Spread Properties para Objetos Asynchronous Iteration (for await…of) Promise.prototype.finally i. Introducción Vamos a ver la primera parte de novedades que nos trae la edición ES9 (ES2018) de JavaScript. Ten en cuenta que no todos los navegadores modernos admiten de forma nativa todas las funcionalidades “nuevas” de JavaScript. Antes de utilizar alguna de ellas en entornos productivos, verifica la compatibilidad en los navegadores objetivo. Si estás utilizando un transpilador como Babel o algún sistema de build moderno (por ejemplo, Vite, Webpack con configuración adecuada o Next.js con configuración predeterminada), no deberías preocuparte, ya que estas herramientas se encargan de transpilar el código a versiones compatibles con navegadores más antiguos. Aunque la mayoría de estas funcionalidades ya cuentan con un amplio soporte en los navegadores actuales, siempre es recomendable hacer una validación explícita antes de lanzar a producción. ii. Nuevas Características 01. Rest [ref] y Spread [ref] Properties para Objetos Desde ECMAScript 2015 (ES6), la sintaxis ... ya se utilizaba ampliamente en JavaScript para trabajar con arreglos: ...spread: para copiar o combinar arrays. ...rest: para capturar elementos restantes al desestructurar. const arr = [1, 2, 3]; // Spread: copia propiedades const newArr = [...arr, 4]; console.log(newArr); // [1, 2, 3, 4] // Rest: extrae las propiedades const [first, ...rest] = arr; console.log(first); // 1 console.log(rest); // [2, 3] Sin embargo, esta sintaxis no era compatible con objetos, lo que forzaba a los desarrolladores a usar métodos como Object.assign() para copiar, clonar o fusionar objetos. ECMAScript 2018 vino a resolver esto al extender la capacidad de ...rest y ...spread a objetos, con una sintaxis similar pero adaptada para trabajar con claves y valores. Esto permite: Extraer propiedades restantes de un objeto (rest). Clonar o combinar objetos (spread). Principales características — Sintaxis limpia y declarativa para operaciones comunes con objetos. Funciona con propiedades enumerables propias (own enumerable). Compatible con desestructuración y funciones. Ventajas — Reemplaza la necesidad de Object.assign para copias superficiales. Permite evitar mutaciones accidentales. Mejora la legibilidad y expresividad del código moderno. ¿Dónde se usa? — Filtrar un dato antes de enviar a una API (si no es necesario). Separar datos para audit logs o sistemas de monitoreo. Configuración de aplicaciones (theme, locale, features, etc.). Formularios donde el usuario modifica los valores por defecto. Composición de props en React. Sintaxis — const obj1 = { a: 1, b: 2 }; // Spread: copia propiedades de obj const obj2 = { ...obj1, c: 3 }; console.log(obj2); // { a: 1, b: 2, c: 3 } // Rest: extrae las propiedades restantes const { a, ...rest } = obj2; console.log(a); // 1 console.log(rest); // { b: 2, c: 3 } ¿Cómo funciona internamente? — Durante la desestructuración: El motor recopila las propiedades no extraídas en un nuevo objeto ([[Rest]]). Spread es una forma sintáctica de llamar internamente a Object.assign({}, ...) con preservación del orden. Ejemplo 1 — Desestructuración con rest const obj = { x: 1, y: 2, z: 3 }; const { x, ...rest } = obj; console.log(x); // 1 console.log(rest); // { y: 2, z: 3 } Donde: const { x, ...rest } = obj; toma la propiedad x y crea una nueva variable llamada rest que contiene todas las propiedades restantes del objeto original. En este caso, rest es un nuevo objeto { y: 2, z: 3 }. Útil cuando: Deseas acceder a una o dos propiedades específicas y pasar el resto a otro componente, función o API. Ejemplo 2 — Desestructuración desde los params de la función function logUser({ id, ...userInfo }) { console.log('ID:', id); // ID: 42 console.log('Datos:', userInfo); // Datos: { name: 'Mauricio', role: 'user' } // ... } logUser({ id: 42, name: 'Mauricio', role: 'user' }); Donde: Desestructuramos el objeto para extraer id y dejamos el resto (name, role) dentro de userInfo. Útil cuando: Deseas separar identificadores o metadatos de los datos operativos. Ejemplo 3 — Overriding de configuraciones const defaults = { theme: 'dark', layout: 'grid' }; const userSettings = { layout: 'list' }; const finalConfig = { ...defaults, ...userSettings }; console.log(finalConfig); // { theme: 'dark', layout: 'list' } Donde: Se combinan dos objetos (defaults y userSettings) con spread. Las propiedades del segundo (userSettings) sobrescriben las del primero si coinciden. Ejemplo 4 — React: Composición de props con spread function Button(props) { const { children, ...rest } = props; return {children}; } // Uso: Guardar Donde: Separam

Temario
- Introducción
- Nuevas Características
- Rest y Spread Properties para Objetos
- Asynchronous Iteration (for await…of)
- Promise.prototype.finally
i. Introducción
Vamos a ver la primera parte de novedades que nos trae la edición ES9 (ES2018) de JavaScript.
Ten en cuenta que no todos los navegadores modernos admiten de forma nativa todas las funcionalidades “nuevas” de JavaScript.
Antes de utilizar alguna de ellas en entornos productivos, verifica la compatibilidad en los navegadores objetivo.
Si estás utilizando un transpilador como Babel o algún sistema de build moderno (por ejemplo, Vite, Webpack con configuración adecuada o Next.js con configuración predeterminada), no deberías preocuparte, ya que estas herramientas se encargan de transpilar el código a versiones compatibles con navegadores más antiguos.
Aunque la mayoría de estas funcionalidades ya cuentan con un amplio soporte en los navegadores actuales, siempre es recomendable hacer una validación explícita antes de lanzar a producción.
ii. Nuevas Características
01. Rest [ref] y Spread [ref] Properties para Objetos
Desde ECMAScript 2015 (ES6), la sintaxis ...
ya se utilizaba ampliamente en JavaScript para trabajar con arreglos:
-
...spread
: para copiar o combinar arrays. -
...rest
: para capturar elementos restantes al desestructurar.
const arr = [1, 2, 3];
// Spread: copia propiedades
const newArr = [...arr, 4];
console.log(newArr); // [1, 2, 3, 4]
// Rest: extrae las propiedades
const [first, ...rest] = arr;
console.log(first); // 1
console.log(rest); // [2, 3]
Sin embargo, esta sintaxis no era compatible con objetos, lo que forzaba a los desarrolladores a usar métodos como Object.assign()
para copiar, clonar o fusionar objetos.
ECMAScript 2018 vino a resolver esto al extender la capacidad de ...rest
y ...spread
a objetos, con una sintaxis similar pero adaptada para trabajar con claves y valores. Esto permite:
- Extraer propiedades restantes de un objeto (
rest
). - Clonar o combinar objetos (
spread
).
Principales características —
- Sintaxis limpia y declarativa para operaciones comunes con objetos.
- Funciona con propiedades enumerables propias (
own enumerable
). - Compatible con desestructuración y funciones.
Ventajas —
- Reemplaza la necesidad de
Object.assign
para copias superficiales. - Permite evitar mutaciones accidentales.
- Mejora la legibilidad y expresividad del código moderno.
¿Dónde se usa? —
- Filtrar un dato antes de enviar a una API (si no es necesario).
- Separar datos para audit logs o sistemas de monitoreo.
- Configuración de aplicaciones (
theme
,locale
,features
, etc.). - Formularios donde el usuario modifica los valores por defecto.
- Composición de props en React.
Sintaxis —
const obj1 = { a: 1, b: 2 };
// Spread: copia propiedades de obj
const obj2 = { ...obj1, c: 3 };
console.log(obj2); // { a: 1, b: 2, c: 3 }
// Rest: extrae las propiedades restantes
const { a, ...rest } = obj2;
console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }
¿Cómo funciona internamente? —
Durante la desestructuración:
- El motor recopila las propiedades no extraídas en un nuevo objeto (
[[Rest]]
). -
Spread
es una forma sintáctica de llamar internamente aObject.assign({}, ...)
con preservación del orden.
Ejemplo 1 — Desestructuración con rest
const obj = { x: 1, y: 2, z: 3 };
const { x, ...rest } = obj;
console.log(x); // 1
console.log(rest); // { y: 2, z: 3 }
Donde:
-
const { x, ...rest } = obj;
toma la propiedadx
y crea una nueva variable llamadarest
que contiene todas las propiedades restantes del objeto original. - En este caso,
rest
es un nuevo objeto{ y: 2, z: 3 }
.
Útil cuando: Deseas acceder a una o dos propiedades específicas y pasar el resto a otro componente, función o API.
Ejemplo 2 — Desestructuración desde los params
de la función
function logUser({ id, ...userInfo }) {
console.log('ID:', id); // ID: 42
console.log('Datos:', userInfo); // Datos: { name: 'Mauricio', role: 'user' }
// ...
}
logUser({ id: 42, name: 'Mauricio', role: 'user' });
Donde: Desestructuramos el objeto para extraer id
y dejamos el resto (name
, role
) dentro de userInfo
.
Útil cuando: Deseas separar identificadores o metadatos de los datos operativos.
Ejemplo 3 — Overriding de configuraciones
const defaults = { theme: 'dark', layout: 'grid' };
const userSettings = { layout: 'list' };
const finalConfig = { ...defaults, ...userSettings };
console.log(finalConfig); // { theme: 'dark', layout: 'list' }
Donde:
- Se combinan dos objetos (
defaults
yuserSettings
) conspread
. - Las propiedades del segundo (
userSettings
) sobrescriben las del primero si coinciden.
Ejemplo 4 — React: Composición de props con spread
function Button(props) {
const { children, ...rest } = props;
return <button {...rest}>{children}</button>;
}
// Uso:
<Button className="btn-primary" onClick={handleClick}>
Guardar
</Button>
Donde: Separamos children
para controlarlo, y propagamos el resto (className
, onClick
, etc.).
Útil ya que: Es un patrón común en componentes reutilizables para no “re-declarar” cada prop.
Ejemplo 5 — Filtrado de campos sensibles
function removeSensitiveFields(user) {
const { password, ssn, ...safeData } = user;
return safeData;
}
const sanitized = removeSensitiveFields({
name: 'John',
email: 'john@example.com',
password: '123456',
ssn: '999-99-9999',
});
console.log(sanitized); // { name: 'John', email: 'john@example.com' }
Donde: Antes de mostrar o guardar el objeto en logs, se eliminan campos sensibles con rest
.
Ejemplo 6 — Merging de resultados API + locale override
const apiResponse = {
title: 'Avengers',
description: 'Superhero movie',
rating: 8.7,
};
const localeOverrides = {
title: 'Los Vengadores',
};
const finalData = { ...apiResponse, ...localeOverrides };
// { title: 'Los Vengadores', description: 'Superhero movie', rating: 8.7 }
Donde: Usamos spread
para adaptar el contenido a distintos idiomas o preferencias sin modificar el resultado original.
Riesgos comunes —
Ejemplo 1— Spread
no aplica como copia profunda
const original = {
user: {
name: 'Mauricio',
age: 40,
},
active: true,
};
// "Clonamos"
const copy = { ...original };
// Modificamos una propiedad del objeto anidado
copy.user.age = 55;
console.log(copy.user.age); // 55
console.log(original.user.age); // 55 - también cambió el original!!!
Aunque copy
parece una copia nueva de original
, la propiedad user
sigue siendo la misma referencia en ambos objetos. Esto ocurre porque ...original
solo hace shallow copy, es decir:
- Copia propiedades de primer nivel (clave/valor).
- Si ese valor es un objeto, la referencia se copia, no el contenido.
Si necesitas una copia profunda (deep copy), debes usar una estrategia adicional como:
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.user.age = 55;
console.log(deepCopy.user.age); // 55
console.log(original.user.age); // 40
Ejemplo 2—rest
no aplica como copia profunda
const original = {
user: {
name: 'Angie',
age: 35,
},
active: true,
};
// Usamos rest para separar propiedades
const { active, ...rest } = original;
// Modificamos el objeto anidado en `rest`
rest.user.age = 21;
console.log(rest.user.age); // 21
console.log(original.user.age); // 21 - también cambió el original!!!
- El operador
...rest
creó un nuevo objeto con todas las propiedades exceptoactive
. - Sin embargo, la propiedad
user
dentro derest
sigue siendo la misma referencia que enoriginal
.
Si deseas evitar ese riesgo, deberías hacer una deep copy manual o asistida:
const { active, ...rest } = original;
const safeRest = {
...rest,
user: { ...rest.user }, // copia anidada
};
safeRest.user.age = 21;
console.log(safeRest.user.age); // 21
console.log(original.user.age); // 35
Soporte —
A considerar —
- No hace copia profunda (shallow copy).
- Solo propiedades propias, no del prototipo.
- Si hay propiedades repetidas, la última sobrescribe.
- Usa spread/rest con precaución: no reemplaza lógica de validación profunda.
02. Asynchronous Iteration (for await...of
) [ref]
for await...of
permite iterar asincrónicamente sobre objetos async iterables (como streams, APIs paginadas, generadores async, etc.).
Principales características —
- Funciona sobre objetos con
Symbol.asyncIterator
[ref]. - Pausa la ejecución hasta que cada
Promise
se resuelve. - Se puede usar dentro de funciones
async
.
Ventajas —
- Facilita el trabajo con flujos de datos asincrónicos.
- Código más limpio comparado con
.then()
oforEach + async
.
Sintaxis —
async function process() {
const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield "uno";
yield "dos";
yield "tres";
},
};
for await (const val of asyncIterable) {
console.log(val);
}
}
Explicando la sintaxis —
Paso 1: Declaración de una función async
async function process() { ... }
Nota: Toda función
async
devuelve automáticamente una promesa y nos permite usarawait
dentro de ella.
Paso 2: Crear un objeto con un async iterable
const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield "uno";
yield "dos";
yield "tres";
},
};
Donde:
- Al llamarse
asyncIterable[Symbol.asyncIterator]()
, devuelve un AsyncGenerator (objeto asincrónicamente iterable). - Este generador produce (
yield
) valores uno a uno, como una secuencia. - Cada
yield
se envuelve automáticamente en unaPromise
.
Paso 3: Usar for await...of
for await (const val of asyncIterable) {
console.log(val);
}
Donde:
- Itera de manera asincrónica sobre un objeto que implemente
Symbol.asyncIterator
. - Espera con
await
a que cadayield
produzca un valor. - Asigna ese valor a
val
y ejecuta el cuerpo del bloqueconsole.log(val)
.
Resumiendo:
-
for await
llama aasyncIterator()
y entra al generador. - Primer
yield
—"uno"
se resuelve y se imprime. - Segundo
yield
—"dos"
se resuelve y se imprime. - Tercer
yield
—"tres"
se resuelve y se imprime. - Generador finaliza —
done: true
el bucle termina.
Ejemplo 1: Delays simulados
async function* delayedValues() {
const delays = [4000, 2000, 3000];
for (const ms of delays) {
await new Promise(res => setTimeout(res, ms));
yield ms;
}
}
async function logDelays() {
for await (const ms of delayedValues()) {
console.log(`Esperado: ${ms}ms`);
}
}
logDelays();
Donde:
delayedValues
es un generador asincrónico (async function*
) que:
- Espera 4s, luego devuelve
4000
. - Espera 2s, luego devuelve
2000
. - Espera 3s, luego devuelve
3000
.
logDelays
usa for await...of
para:
- Iterar sobre cada valor generado por
delayedValues
. - Esperar que cada
Promise
se resuelva antes de continuar. - Imprimir:
Esperado: 4000ms
,Esperado: 2000ms
,Esperado: 3000ms
.
Útil para: simular tareas asincrónicas que se resuelven en tiempos diferentes (como llamadas a APIs que tardan distinto).
Ejemplo 2 — Paginación de películas populares con TMDb + for await...of
const API_KEY = 'TU API KEY';
const BASE_URL = 'https://api.themoviedb.org/3/movie/popular';
async function fetchMoviesPage(page) {
const url = `${BASE_URL}?language=en-US&page=${page}`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}`}
});
if (!res.ok) throw new Error(`Error HTTP ${res.status}`);
return res.json();
}
// Generador que devuelve todas las películas de TMDb
async function* fetchAllPopularMovies(maxPages = 3) {
let page = 1;
while (page <= maxPages) {
// Invocamos la función que devuelve las películas de la página actual
const data = await fetchMoviesPage(page);
// Recorremos los resultados de la página actual y devolvemos la película actual
for (const movie of data.results) {
yield movie;
}
if (page >= data.total_pages) break;
page++;
}
}
// Función principal que muestra las películas de TMDb
async function main() {
console.log('Películas populares de TMDb:');
for await (const movie of fetchAllPopularMovies()) {
console.log(`= ${movie.title} (${movie.release_date})`);
}
}
// Llamamos a la función principal
main().catch(console.error);
Donde:
-
fetchAllPopularMovies()
es un async generator que va página por página. - Usa
yield
para entregar una película a la vez. -
main()
consume las películas una por una confor await...of
.
Útil para: Procesamiento en batch, consolas, scripts backend, testing, pre-fetch de datos al montar componentes en Next.js.
Soporte —
A considerar —
- Solo puede usarse dentro de funciones
async
. - Ideal para procesar streams, resultados paginados, o APIs con espera.
- No mezclarlo con
.map()
o.forEach()
si cada paso depende del anterior. - Prefiere
for await...of
cuando el orden importa y hay espera real.
03.Promise.prototype.finally
.finally()
es un nuevo método en la cadena de Promesas que se ejecuta después de que la promesa se resuelve o rechaza, sin modificar el valor original.
Principales características —
- Se ejecuta siempre, sin importar si fue
.then
o.catch
. - No cambia el resultado de la promesa.
- Ideal para limpiar recursos o resetear estados.
Ventajas —
- Reemplaza patrones repetitivos como duplicar
setLoading(false)
en.then
y.catch
. - Mejora la claridad y estructura del código asincrónico.
Sintaxis —
fetch('/api/data')
.then(res => res.json())
.catch(err => console.error(err))
.finally(() => console.log('Petición finalizada'));
- Se ejecuta después de
.then()
o.catch()
. - No recibe parámetros.
- Retorna una promesa.
- Si lanzas un error o devuelves una promesa rechazada dentro de
.finally()
, ese error reemplaza el flujo
Ejemplo 1 — Simulación de espera
function simulateRequest() {
return new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
success ? resolve('Operación exitosa') : reject('Ocurrió un error');
}, 1000);
});
}
simulateRequest()
.then(console.log)
.catch(console.log)
.finally(() => {
console.log('entra a finally');
});
Ejemplo 2 — Ocultar un loader en React
'use client';
import { useState, useEffect } from 'react';
interface ProfileProps {
name: string;
email: string;
}
function ProfileLoader() {
const [loading, setLoading] = useState(false);
const [profile, setProfile] = useState<ProfileProps | null>(null);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('https://jsonplaceholder.typicode.com/users/1')
.then((res) => {
if (!res.ok) throw new Error('Error al cargar');
return res.json();
})
.then(setProfile)
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Cargando...</p>;
if (error) return <p>Error: {error}</p>;
return profile ? (
<div>
<h1>{profile.name}</h1>
<p>{profile.email}</p>
</div>
) : null;
}
export default ProfileLoader;
Soporte —
A considerar —
- No devuelvas nuevos valores dentro de
.finally()
ya que se se ignoran. - Usalo para tareas neutrales como limpiar loaders, cerrar conexiones, etc.
- Evita lógica pesada en
.finally()
.