Combinadores de Promesas
Desde la introducción de las promesas en ES2015, JavaScript ha soportado exactamente dos combinadores de promesas: los métodos estáticos Promise.all
y Promise.race
.
Actualmente, dos nuevas propuestas están atravesando el proceso de estandarización: Promise.allSettled
, y Promise.any
. Con estas adiciones, habrá un total de cuatro combinadores de promesas en JavaScript, cada uno permitiendo diferentes casos de uso.
Aquí tienes una visión general de los cuatro combinadores:
nombre | descripción | estado |
---|---|---|
Promise.allSettled | no se detiene en el primer fallo | añadido en ES2020 ✅ |
Promise.all | se detiene cuando un valor de entrada es rechazado | añadido en ES2015 ✅ |
Promise.race | se detiene cuando un valor de entrada se resuelve | añadido en ES2015 ✅ |
Promise.any | se detiene cuando un valor de entrada se cumple | añadido en ES2021 ✅ |
Vamos a ver un caso de uso para cada combinador.
Promise.all
Promise.all
te permite saber cuándo todas las promesas de entrada han sido cumplidas o cuándo una de ellas ha sido rechazada.
Imagina que un usuario hace clic en un botón y quieres cargar algunos estilos para poder renderizar una interfaz de usuario completamente nueva. Este programa inicia una solicitud HTTP para cada hoja de estilo en paralelo:
const promises = [
fetch('/component-a.css'),
fetch('/component-b.css'),
fetch('/component-c.css'),
];
try {
const styleResponses = await Promise.all(promises);
enableStyles(styleResponses);
renderNewUi();
} catch (reason) {
displayError(reason);
}
Solo quieres empezar a renderizar la nueva interfaz de usuario una vez que todas las solicitudes hayan tenido éxito. Si algo sale mal, prefieres mostrar un mensaje de error lo antes posible, sin esperar a que termine el resto del trabajo.
En tal caso, podrías usar Promise.all
: quieres saber cuándo todas las promesas han sido cumplidas, o tan pronto como una de ellas sea rechazada.
Promise.race
Promise.race
es útil si quieres ejecutar múltiples promesas, y ya sea…
- hacer algo con el primer resultado exitoso que llegue (en caso de que se cumpla alguna de las promesas), o
- hacer algo tan pronto como una de las promesas sea rechazada.
Es decir, si una de las promesas es rechazada, quieres preservar ese rechazo para tratar el caso de error por separado. El siguiente ejemplo hace exactamente eso:
try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
renderResult(result);
} catch (error) {
renderError(error);
}
Iniciamos una tarea computacionalmente costosa que podría tomar mucho tiempo, pero la comparamos con una promesa que rechaza después de 2 segundos. Dependiendo de cuál promesa se cumpla o rechace primero, renderizamos el resultado calculado o el mensaje de error en dos rutas de código separadas.
Promise.allSettled
Promise.allSettled
te da una señal cuando todas las promesas de entrada están resueltas, lo que significa que están cumplidas o rechazadas. Esto es útil en casos donde no te importa el estado de la promesa, solo quieres saber cuándo el trabajo está terminado, sin importar el éxito o el fallo.
Por ejemplo, puedes iniciar una serie de llamadas API independientes y usar Promise.allSettled
para asegurarte de que todas se completen antes de hacer algo más, como eliminar un indicador de carga:
const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imagina que algunas de estas solicitudes fallan y otras tienen éxito.
await Promise.allSettled(promises);
// Todas las llamadas API han terminado (ya sea que hayan fallado o tenido éxito).
removeLoadingIndicator();
Promise.any
Promise.any
te proporciona una señal tan pronto como una de las promesas se cumple. Esto es similar a Promise.race
, excepto que any
no rechaza anticipadamente cuando una de las promesas falla.
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// Alguna de las promesas fue cumplida.
console.log(first);
// → por ejemplo, 'b'
} catch (error) {
// Todas las promesas fueron rechazadas.
console.assert(error instanceof AggregateError);
// Registrar los valores de rechazo:
console.log(error.errors);
// → [
// <TypeError: Failed to fetch /endpoint-a>,
// <TypeError: Failed to fetch /endpoint-b>,
// <TypeError: Failed to fetch /endpoint-c>
// ]
}
Este ejemplo de código verifica qué endpoint responde más rápido y luego lo registra. Solo si todas las solicitudes fallan terminamos en el bloque catch
, donde podemos manejar los errores.
Los rechazos de Promise.any
pueden representar múltiples errores a la vez. Para admitir esto a nivel de lenguaje, se introduce un nuevo tipo de error llamado AggregateError
. Además de su uso básico en el ejemplo anterior, los objetos AggregateError
también pueden ser construidos programáticamente, al igual que otros tipos de errores:
const aggregateError = new AggregateError([errorA, errorB, errorC], '¡Algo salió mal!');