Encadenamiento de Promises
El encadenamiento de Promises consiste en conectar varias operaciones asincrónicas de manera secuencial usando el método .then(). Cada .then() recibe el resultado de la Promise anterior y devuelve un nuevo valor o una nueva Promise.
Permite estructurar el flujo de forma lineal, evitando el callback hell y manejando los errores centralizadamente.
Ejemplo técnico
loginUser("user@example.com")
.then((user) => loadProfile(user.id))
.then((profile) => getNotifications(profile.id))
.then((notifications) => {
console.log("Notificaciones:", notifications);
})
.catch((error) => {
console.error("Error en la cadena:", error);
});
¿Qué está ocurriendo en cada paso?
loginUser()devuelve una Promise.- El primer
.then()recibe el user y retorna la Promise deloadProfile(). - El segundo
.then()recibe el profile y retorna la Promise degetNotifications(). - Finalmente, el tercer
.then()procesa las notificaciones. - Cualquier error en la cadena es capturado en el
.catch()final.
Diagrama de secuencias
Valores de retorno en .then()
Un .then() puede devolver:
-
Un valor normal:
Se pasa directamente al siguiente
.then():Promise.resolve(2)
.then((num) => num * 2) // devuelve 4
.then((result) => console.log(result)); // 4 -
Otra Promise:
El siguiente
.then()esperará a que esta Promise se resuelva:Promise.resolve(2)
.then((num) => Promise.resolve(num * 2))
.then((result) => console.log(result)); // 4 -
Nada:
Si no devuelves nada, el siguiente
.then()recibiráundefined.
Encadenamiento de errores
Si ocurre un error en cualquier Promise de la cadena, se salta al primer .catch():
loginUser("<user@example.com>")
.then((user) => loadProfile(user.id))
.then(() => {
throw new Error("Error forzado");
})
.then(() => console.log("Esto no se ejecuta"))
.catch((err) => console.error("Capturado:", err));
Una vez capturado el error, puedes continuar la cadena devolviendo un valor o lanzando otro error.
Por ejemplo, miremos un ejemplo con un flujo de compra:
processPayment(order)
.then((paymentResult) => createInvoice(paymentResult))
.then((invoice) => sendEmail(invoice))
.then(() => console.log("Compra completada"))
.catch((err) => console.error("Error en la compra:", err));
Cada paso depende del resultado del anterior. El .catch() centraliza el manejo de errores.
Buenas prácticas en Promise chaining
- Siempre devuelve algo en cada
.then(): un valor o una Promise. - Coloca el
.catch()al final de la cadena para capturar cualquier error. - No anides
.then()dentro de otro.then(); el objetivo es mantener el flujo plano. - Usa
finally()si necesitas ejecutar lógica independientemente del resultado:
Por ejemplo:
doTask()
.then(handleSuccess)
.catch(handleError)
.finally(() => console.log("Siempre se ejecuta"));
Relación con el Event Loop
Cada .then() se encola en la Microtask Queue cuando la Promise se resuelve. Se ejecutan antes de cualquier macrotarea (por ejemplo, setTimeout).
setTimeout(() => console.log("Macrotarea"), 0);
Promise.resolve().then(() => console.log("Microtarea 1"));
Promise.resolve().then(() => console.log("Microtarea 2"));
console.log("Síncrono");
Salida:
Síncrono
Microtarea 1
Microtarea 2
Macrotarea
Propagación de errores en Promises chaining
Apliquemos el flujo en un ejemplo:
Promise.resolve("Inicio")
.then((data) => {
console.log(data);
throw new Error("Error en el primer then");
})
.then(() => {
console.log("Esto no se ejecuta");
})
.catch((error) => {
console.error("Error capturado:", error.message);
return "Valor de recuperación";
})
.then((data) => {
console.log("Se recupera el flujo con:", data);
})
.finally(() => {
console.log("Siempre se ejecuta el finally");
});
La salida esperada será:
Inicio
Error capturado: Error en el primer then
Se recupera el flujo con: Valor de recuperación
Siempre se ejecuta el finally
Referencias
- Flanagan, D. (2020). JavaScript: The Definitive Guide (7th ed.). O’Reilly Media.
- Mozilla Developer Network. (s.f.). Using Promises.
- Mozilla Developer Network. (s.f.). Promise.prototype.catch().
- TypeScript Handbook. (s.f.). Promises.
- Google. (s.f.). JavaScript Style Guide.