</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #2

Promesas en JavaScript: guía práctica

Entiende las promesas de JavaScript — cómo crearlas, then/catch/finally, el encadenamiento y combinarlas con Promise.all, race, any y allSettled — con ejemplos ejecutables y los errores más comunes.

Una pieza de rompecabezas roja encajando en el último hueco de un puzle gris

Una promesa es un objeto que representa un valor que aún no está listo. Es la base del JavaScript asíncrono: fetch(), los temporizadores, las lecturas de archivos en Node y la mayoría de las API modernas devuelven promesas. Una promesa siempre está en uno de tres estados — pendiente (pending), cumplida (fulfilled) o rechazada (rejected) — y una vez que se resuelve no vuelve a cambiar.

Crear una promesa

La mayor parte del tiempo consumes promesas que te entrega una API. Pero puedes crear una con el constructor Promise, que recibe una función ejecutora con resolve y reject:

const wait = (ms) => new Promise((resolve) => {
  setTimeout(resolve, ms);
});

const fetchUser = (id) => new Promise((resolve, reject) => {
  if (!id) reject(new Error('id requerido'));
  else resolve({ id, name: 'Ada' });
});

Llamar a resolve(valor) cumple la promesa con ese valor; llamar a reject(error) la rechaza. Cualquier excepción lanzada de forma síncrona dentro de la ejecutora también rechaza la promesa.

then, catch y finally

Lees el resultado de una promesa con .then() para el éxito, .catch() para el fallo y .finally() para una limpieza que se ejecuta en ambos casos:

fetchUser(42)
  .then((user) => console.log('Recibido', user.name))
  .catch((err) => console.error('Falló:', err.message))
  .finally(() => console.log('Hecho'));

.then() puede recibir dos argumentos — onFulfilled y onRejected — pero un .catch() separado al final de la cadena es más claro y también captura los errores lanzados en los .then() anteriores.

El encadenamiento: el verdadero superpoder

Cada .then() devuelve una nueva promesa, por eso se encadenan. Lo que devuelvas de un manejador se convierte en la entrada del siguiente. Y lo más importante: si devuelves una promesa, la cadena espera a que se resuelva antes de continuar:

fetch('/api/user')
  .then((res) => res.json())          // devuelve una promesa → la cadena espera
  .then((user) => fetch('/api/posts?user=' + user.id))
  .then((res) => res.json())
  .then((posts) => console.log(posts.length, 'publicaciones'))
  .catch((err) => console.error(err));

Un único .catch() al final maneja un rechazo de cualquier paso anterior, por lo que el encadenamiento supera a anidar callbacks.

Velocistas de relevos en una pista pasándose el testigo de una corredora a otra
Como corredoras de relevos que se pasan el testigo, una cadena de promesas entrega el resultado de cada paso al siguiente manejador, en orden.

Ejecutar promesas juntas

Cuando las tareas no dependen unas de otras, lánzalas en paralelo y combina los resultados. Cuatro métodos estáticos cubren los casos habituales:

  • Promise.all([...]) — espera a que todas se cumplan; rechaza en cuanto una se rechaza. Devuelve un array de resultados en orden.
  • Promise.allSettled([...]) — espera a que todas terminen y nunca rechaza; devuelve un array de objetos { status, value } o { status, reason }.
  • Promise.race([...]) — se resuelve con la primera promesa que se resuelva, ya sea cumplida o rechazada.
  • Promise.any([...]) — se resuelve con la primera promesa cumplida; solo rechaza si todas se rechazan.
// Obtener tres recursos a la vez, fallar si alguno falla
const [user, posts, prefs] = await Promise.all([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/posts').then((r) => r.json()),
  fetch('/api/prefs').then((r) => r.json()),
]);

// Igual, pero tolerando fallos individuales
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
results.forEach((r) => {
  if (r.status === 'fulfilled') console.log('ok', r.value);
  else console.warn('falló', r.reason);
});

Errores comunes

  • Olvidar el return. Dentro de un .then(), si llamas a una promesa pero no la return, la cadena no la espera — pierdes el orden y el manejo de errores.
  • Rechazos no manejados. Una promesa sin .catch() (o sin try/catch alrededor de await) produce un unhandledrejection. Maneja siempre los errores al final de la cadena.
  • Mezclar callbacks y promesas. No llames a resolve dos veces, ni a resolve y reject a la vez — solo cuenta la primera llamada; el resto se ignora en silencio.
  • La ejecutora corre de inmediato. La función que pasas a new Promise() se ejecuta de forma síncrona, al instante — solo los callbacks de .then() se difieren.

Referencia rápida

ObjetivoMétodo
Manejar el éxito.then(onFulfilled)
Manejar el fallo.catch(onRejected)
Limpiar en ambos casos.finally(fn)
Todas deben tener éxitoPromise.all
Recoger cada resultadoPromise.allSettled
La primera en resolversePromise.race
La primera en cumplirsePromise.any

Las promesas son la fontanería bajo async/await, que no es más que una sintaxis más agradable sobre los mismos objetos. Una vez que el encadenamiento y los cuatro combinadores te resulten naturales, el JavaScript asíncrono deja de intimidar.