Encadenamiento de Promesas en JavaScript
Aprende cómo funciona el encadenamiento de promesas en JavaScript para ejecutar operaciones asíncronas de forma secuencial con .then(), .catch() y .finally().
El encadenamiento de promesas permite ejecutar operaciones asíncronas una tras otra, donde cada paso comienza solo después de que el anterior haya finalizado. Se adjunta una secuencia de manejadores .then() a una promesa, y cada manejador recibe el resultado del paso anterior.
Antes de las promesas, secuenciar trabajo asíncrono significaba anidar callbacks dentro de callbacks — el infame "callback hell" o "pirámide de la perdición":
queryDatabase('users', (users) => {
queryDatabase('posts', (posts) => {
queryDatabase('comments', (comments) => {
// deeply nested, hard to read, error handling duplicated everywhere
});
});
});El encadenamiento aplana esa pirámide en una secuencia legible de arriba hacia abajo con un único lugar para manejar errores. Esta página cubre cómo funciona el encadenamiento en la práctica, el error más común (un return faltante), la recuperación de errores y la limpieza. Para la sintaxis relacionada que se basa en las promesas, consulta JavaScript: async/await.
Cómo Funciona el Encadenamiento: Cada .then() Devuelve una Nueva Promesa
Este es el mecanismo central, y todo lo demás se deriva de él. .then() no devuelve la promesa original — devuelve una promesa completamente nueva. Lo que esa nueva promesa resuelve depende de lo que devuelva tu manejador:
- Devolver un valor simple → el siguiente
.then()recibe ese valor. - Devolver una promesa → la cadena espera a que se resuelva, y el siguiente
.then()recibe su valor resuelto (no la promesa en sí). - No devolver nada → el siguiente
.then()recibeundefined. - Lanzar un error → la cadena salta directamente al
.catch()más cercano.
Como cada .then() devuelve una nueva promesa, puedes seguir adjuntando llamadas .then() y pasar un valor a lo largo de la cadena:
El caso más potente es devolver una promesa desde un manejador. La cadena se pausa hasta que esa promesa se resuelva antes de continuar, que es exactamente como se secuencian operaciones asíncronas dependientes:
Encadenamiento Básico de Promesas
Considera el escenario en que necesitas consultar una base de datos y luego usar el resultado de esa consulta para hacer otra consulta. Cada .then() devuelve la promesa de la siguiente consulta, por lo que la cadena espera a que una consulta termine antes de comenzar la siguiente:
El Error #1: Olvidar el return (la "cadena desconectada")
Este es el error más común en el encadenamiento de promesas. Si inicias una operación asíncrona dentro de un .then() pero olvidas devolver su promesa, la cadena no espera por ella — el resultado se pierde y el siguiente .then() se ejecuta inmediatamente con undefined. La promesa interna se convierte en una cadena "desconectada" que se ejecuta por su cuenta.
En la versión errónea de abajo, queryDatabase('posts') se llama pero su promesa no se devuelve, por lo que el segundo .then() registra undefined en lugar de las publicaciones:
Agregar return reconecta la cadena. Ahora el segundo .then() espera la consulta de publicaciones y recibe su resultado:
Consejo: las funciones de flecha con un cuerpo de expresión devuelven automáticamente —
.then(r => queryDatabase(r))devuelve la promesa, pero.then(r => { queryDatabase(r); })(con llaves) no lo hace.
Manejo de Errores en Cadenas
Un único .catch() al final de la cadena maneja cualquier error lanzado — o cualquier promesa rechazada — en cualquier paso anterior. Cuando algo falla, la cadena omite todos los .then() restantes y salta directamente al próximo .catch().
En este ejemplo, la primera consulta se rechaza, por lo que el .then() se omite por completo y el control llega a .catch():
Para un análisis más profundo de los patrones de rechazo, consulta Manejo de Errores con Promesas.
.catch() en medio de la cadena para recuperación
Un .catch() no tiene que ser el último eslabón. Colocado en el medio de una cadena, puede manejar un error, devolver un valor alternativo y dejar que la cadena continúe. Esta es la diferencia entre recuperarse de un fallo y abortar toda la secuencia.
A continuación, el primer paso falla, pero un .catch() en medio de la cadena proporciona un valor predeterminado y la cadena sigue adelante:
Un .catch() en medio de la cadena se recupera y reanuda; un .catch() terminal es la red de seguridad final para todo lo que no se recuperó antes.
Limpieza con .finally()
.finally() se ejecuta una vez que la promesa se resuelve — ya sea que se haya cumplido o rechazado. No recibe ningún argumento y no cambia el valor que pasa por la cadena, lo que lo hace ideal para la limpieza que debe ocurrir de cualquier manera: ocultar un spinner, cerrar una conexión o volver a habilitar un botón.
Ejecutar Promesas en Paralelo: Promise.all
Promise.all no es encadenamiento — el encadenamiento es secuencial (uno tras otro), mientras que Promise.all ejecuta promesas en paralelo y espera a que todas terminen. Úsalo cuando las operaciones no dependen entre sí, por lo que no hay razón para esperar una antes de comenzar la siguiente.
Toma un iterable de promesas y devuelve una única promesa que se resuelve con un array de sus resultados, en el mismo orden que la entrada. Cualquier valor que no sea una promesa en el array (como 42 a continuación) se envuelve automáticamente en una promesa resuelta. Si alguna entrada se rechaza, todo Promise.all se rechaza inmediatamente con ese error.
Para Promise.all, Promise.race, Promise.allSettled y los demás combinadores, consulta la API de Promise.
Resumen
- Cada
.then()devuelve una promesa nueva; la cadena se lee de arriba hacia abajo en lugar de anidarse. - Devuelve valores para pasarlos, y devuelve una promesa desde un manejador para que la cadena espere por ella.
- El error más común es un
returnfaltante dentro de.then()— desconecta el trabajo asíncrono interno y el siguiente paso recibeundefined. - Un
.catch()terminal maneja errores de cualquier paso anterior; un.catch()en medio de la cadena puede recuperarse y reanudar. - Usa
.finally()para la limpieza que debe ejecutarse tanto si la cadena tuvo éxito como si falló. - Usa
Promise.allpara trabajo independiente que debe ejecutarse en paralelo — es una herramienta diferente al encadenamiento secuencial. - Cuando te sientas cómodo aquí, async/await te ofrece el mismo comportamiento con una sintaxis de apariencia sincrónica.