Ahora que sabemos ¿Qué son las promesas?, para qué y como se usan, podemos profundizar y aprender más sobre la API Promise nativa de Javascript, mediante la cuál podemos realizar operaciones con grupos de promesas, tanto independientes como dependientes entre sí.
Por norma general, las tareas asíncronas no sabemos cuanto tardarán en responder y/o procesarse, por lo que muchas veces el orden en que se resuelven no será el mismo. Esto en algunos casos no nos importará, pero en otros sí, por lo que hay que tenerlo en cuenta.
Imaginemos el siguiente supuesto, donde hacemos múltiples tareas asíncronas y queremos realizar una tarea cuando las tres estén resueltas, por lo que necesitamos esperar a tener las tres promesas cumplidas.
Quizás nuestra primera aproximación sería la siguiente: recorrer el array de promesas promises
con un .map()
, de modo que creamos un nuevo array derivado con las promesas ya resueltas, esperando por ellas con un await
:
const p1 = fetch("/robots.txt");
const p2 = fetch("/theme.css");
const p3 = fetch("/index.js");
const promises = [p1, p2, p3];
const responses = promises.map(async (promise) => {
return await promise
});
El planteamiento es correcto, sin embargo, el problema es que la función .map()
u otras array functions no permiten el uso de operaciones asíncronas en su interior, ya que trabajan de forma síncrona.
Si lo hacemos, veremos como la constante
responses
en lugar de un array de respuestas como probablemente esperábamos, tendrá un array de promesas. Es decir, tendremos exactamente lo mismo que teníamos inicialmente enpromises
.
Para solucionar este problema, podemos utilizar el objeto Promise
de Javascript, que incorpora varios métodos estáticos que podemos utilizar en nuestro código. Todos devuelven una promesa (son asíncronos) y son los que veremos a continuación:
Métodos | Disponible | Descripción |
---|---|---|
Promise.all( list) |
Acepta sólo si todas las promesas del |
|
Promise.allSettled( list) |
Acepta sólo si todas las promesas del |
|
Promise.any( list) |
Acepta con el valor de la primera promesa del |
|
Promise.race( list) |
Acepta o rechaza según la primera promesa del |
|
Promise.resolve( value) |
Devuelve una promesa cumplida directamente con el |
|
Promise.reject( value) |
Devuelve una promesa rechazada directamente con el |
En los siguientes ejemplos, vamos a utilizar la función fetch()
para realizar varias peticiones y descargar varios archivos diferentes que necesitaremos para nuestras tareas.
El método Promise.all()
funciona como un «todo o nada»: devuelve una promesa que se cumple cuando todas las promesas del Promise.all()
también lo hace.
En nuestro ejemplo, cada uno de los fetch()
tendrá su propia promesa y sólo cuando se hayan descargado los tres archivos de cada petición se cumplirá la promesa del Promise.all()
:
const p1 = fetch("/robots.txt");
const p2 = fetch("/index.css");
const p3 = fetch("/index.js");
const promises = [p1, p2, p3];
// Utilizando async/await
const responses = await Promise.all(promises);
const codes = responses.map(response => response.status);
console.log(codes); // [200, 200, 200]
A Promise.all()
le pasamos un .then()
. En el caso de que alguna se rechace, no se llegará a ejecutar.
También se podría realizar utilizando .then()
en lugar de async/await
:
// Utilizando .then
Promise.all(promises)
.then(responses => {
const codes = responses.map(response => response.status);
console.log(codes); // [200, 200, 200]
});
El método Promise.allSettled()
funciona como un «todas procesadas»: devuelve una promesa que se cumple cuando todas las promesas del
const p1 = fetch("/robots.txt");
const p2 = fetch("https://google.com/index.css");
const p3 = fetch("/index.js");
const promises = [p1, p2, p3];
const results = await Promise.allSettled(promises);
const objects = results.map(result => result);
console.log(objects);
Esta operación nos devuelve un
status
, donde nos indica si cada promesa individual ha sido cumplida o rechazadavalue
, con los valores devueltos por la promesa si se cumple.reason
, con la razón del rechazo de la promesa si no se cumple.En este caso, obtendremos que la primera y última promesa se resuelven (fulfilled), mientras que la segunda nos da un error de CORS y se rechaza (rejected).
El método Promise.any()
funciona como «la primera que se cumpla»: Devuelve una promesa con el valor de la primera promesa individual del
const p1 = fetch("/robots.txt");
const p2 = fetch("/index.css");
const p3 = fetch("/index.js");
const promises = [p1, p2, p3];
const response = await Promise.any(promises);
console.log(response);
Como vemos, Promise.any()
devolverá una respuesta de la primera promesa cumplida.
El método Promise.race()
funciona como una «la primera que se procese»: la primera promesa del Promise.race()
. Si se cumple, devuelve una promesa cumplida, en caso negativo, devuelve una rechazada.
const p1 = fetch("/robots.txt");
const p2 = fetch("/index.css");
const p3 = fetch("/index.js");
const promises = [p1, p2, p3];
const response = await Promise.race([p1, p2, p3]);
console.log(response);
De forma muy similar a la anterior, Promise.race()
devolverá la promesa que se resuelva primero, ya sea cumpliéndose o rechazándose.
Mediante los métodos estáticos Promise.resolve()
y Promise.reject()
podemos devolver una promesa cumplida o rechazada respectivamente sin necesidad de crear una promesa con new Promise()
, algo que podría ser interesante o cómodo en algunos casos.
Observa que la siguiente función doTask()
no es asíncrona:
const doTask = () => {
const number = 1 + Math.floor(Math.random() * 6);
const isEven = number % 2 === 0;
return isEven ? Promise.resolve(number) : Promise.reject(number);
}
En este caso, generamos un número aleatorio y se devuelve una promesa. Cuando el número generado es par se cumple la promesa, cuando es impar, se rechaza. Sin embargo, ten en cuenta que el problema en este caso es que la promesa no «envuelve» toda la función, por lo que si la tarea tardase algún tiempo en generar el número, no podríamos utilizar el .then()
para consumir la promesa.
Estas funciones estáticas se suelen utilizar en muy pocos casos, para mantener cierta compatibilidad en funciones que se espera que devuelvan una promesa.
Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.
Puedes encontrar más sobre mi en Manz.dev