Cómo ejecutar promesas en secuencia, una detrás de la otra, o en paralelo, todas a la vez, manteniendo un código Javascript legible y mantenible.
A estas alturas ya debes de saber lo que son las promesas, pues las hemos abordado en artículos anteriores. Si no es así, te recomiendo leer el artículo de introducción a las promesas.
En este artículo vamos a ir un paso más allá, viendo ejemplos de trabajo con promesas un poco más complejos, combinando varias ejecuciones. Y es que muchas veces las promesas no vienen por separado, sino que tienes que ejecutar varias promesas una detrás de otra, porque unas dependan entre sí, o ejecutarlas todas a la vez y recibir una señal cuando se ha completado el conjunto entero.
Promesas en secuencia
En muchos casos unas promesas dependen de otras, es decir, tienes que esperar que una promesa haya terminado y, con su resultado, ejecutar otra. En estos casos decimos que las promesas deben ejecutarse en secuencia.
El ejemplo más típico de realización de una promesa en secuencia es el que hacemos para resolver una solicitud Ajax mediante Fetch, dado que para obtener el JSON de un servicio web necesitamos encadenar dos promesas. La primera para recibir una respuesta del servidor y la segunda para procesar esa respuesta y quedarnos con el cuerpo en un objeto.
La manera de ejecutar promesas en secuencia es encadenar varios "then", como puedes ver aquí.
fetch("https://jsonplaceholder.typicode.com/todos/")
.then( response => response.json() )
.then( json => console.log(json) )
La función fetch() devuelve una promesa, que se resuelve cuando se recibe la respuesta del servidor. El primer "then" recibe la respuesta del servidor. Puedes examinar la respuesta para ver si es válida o verificar cualquier otra cosa. Sin embargo aquí solamente ejecutamos response.json() para obtener el objeto de respuesta. El método response.json() devuelve otra promesa que se encadena. El segundo "then" se ejecuta cuando se resuelve la segunda promesa y en él recibimos ya el json procesado y podemos hacer cualquier cosa con él.
Nota: Si te interesa explorar más esta modalidad de trabajo con Ajax te recomendamos el artículo sobre fetch.
Date cuenta que, siempre que devolvamos una promesa nueva, podremos enganchar los "then", que esperarán a que la promesa se resuelva para continuar.
Encadenar más promesas en secuencia
El ejemplo se puede complicar todo lo que queramos. Ahora vamos a acceder a dos servicios web y esperar 5 segundos entre uno y otro.
Para el acceso a los servicios web usaremos fetch, igual que antes. Entre medias haremos una pausa para poder espaciar estos accesos. Para poder ver todo esto con distintas promesas hemos creado una función que devuelve una promesa, que simplemente se resuelve después de una espera.
const esperar = tiempo => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, tiempo);
})
}
Este código ilustra cómo podemos crear nuestras propias funciones que devuelven promesas. Si te parece un poco raro o quieres ampliar esta información te recomiendo el artículo sobre cómo crear tus propias promesas.
Ahora veamos el código que ejecuta toda la secuencia de promesas de la que hemos hablado. Las dos promesas del acceso al primer servicio web, la espera de 5 segundos y finalmente las dos promesas de acceso al segundo servicio web.
fetch("https://jsonplaceholder.typicode.com/todos/")
.then( response => response.json())
.then( json => console.log(json))
.then(() => esperar(5000))
.then( res => console.log(res))
.then(() => fetch("https://pokeapi.co/api/v2/pokemon/1"))
.then( response => response.json())
.then( json => console.log(json));
¿Qué te parece? normalmente no tienes tantas promesas que ejecutar en secuencia, pero si fuera el caso puedes hacer un código bastante compacto y evitar lo que se llama la pirámide de callbacks.
Generalmente, cada una de las funciones de los "then" devuelven nuevas promesas que se irán ejecutando cuando acabe la anterior. Sin embargo, esto no tiene por qué ser siempre así. Por ejemplo, por en medio hemos colocado una función de un "then" que no cumple esta norma. ¿La encuentras?
Seguramente la hayas visto, pero si no, es la siguiente:
.then( res => console.log(res))
Esa función callback del "then" no devuelve una promesa. De hecho no devuelve ningún valor. Esa situación no es un problema en realidad, simplemente el próximo "then" se ejecutará inmediatamente después del anterior, sin tener que esperar nada, puesto que no se ha devuelto una promesa que tarde en resolverse.
Ejecutar promesas en paralelo
No siempre se necesitan ejecutar todas las promesas una detrás de otra. Muchas veces no son dependientes entre sí y podemos ejecutarlas a la vez, para ahorrar tiempo.
Podríamos simplemente colocar el código de todas las promesas suelto e independiente entre sí. Algo como:
esperar(5000).then((res) => console.log("Uno"));
esperar(1000).then((res) => console.log("Dos"));
esperar(3000).then((res) => console.log("Tres"));
console.log("Fin!");
¿En qué orden piensas que se mostrarán los distintos mensajes a la consola?
Seguramente lo has adivinado…. será: "Fin! Dos Tres Uno".
Pero a veces tienes que esperar a que se ejecuten todas las promesas para luego hacer alguna acción con todos los valores de respuesta. Por ejemplo, dado el código anterior nos gustaría que el mensaje "Fin!" fuera el último que se mostrase.
Quizás en este ejemplo está claro porque sabemos que la promesa que más tiempo va a tardar en ejecutarse es la primera, que espera 5 segundos. Podríamos escribir el mensaje "Fin!" cuando ella termine. Sin embargo, en la mayoría de las ocasiones no sabemos qué promesa va a terminar más tarde y queremos asegurarnos que todas hayan acabado antes de hacer nada. Para estos casos tenemos "Promise.all".
La clase Promise de Javascript tiene un método llamado all() que recibe un array de promesas. Las ejecuta todas y, cuando acaba la última, se devuelve un array con todos los valores devueltos por las promesas.
Promise.all([
esperar(5000),
esperar(1000),
esperar(3000)
]).then( respuestas => {
console.log("Fin!");
});
Ahora el mensaje "Fin" se mostrará por último, una vez acaben todas las promesas. Por tanto, y dado que se ejecutan en paralelo, tardará 5 segundos en total en aparecer "Fin".
Si quisiéramos trabajar con los valores de devolución de las promesas, podemos obtenerlos con el array de respuestas que nos devuelve Promise.all(). Por ejemplo así mostraríamos todas las respuestas haciendo un recorrido al array.
Promise.all([
esperar(5000),
esperar(1000),
esperar(3000)
]).then( respuestas => {
for (let i in respuestas) {
console.log(respuestas[i]);
}
}
);
Conclusión
Las promesas son ideales para la programación asíncrona, porque mantienen el orden en el código y la sencillez, con lo que también tus programas serán más claros y fáciles de extender o depurar. En este artículo has podido aprender a manejar promesas de una manera más avanzada, que seguramente te será de utilidad más de una vez en tus aplicaciones Javascript.
Ahora sabes ejecutar promesas en secuencia, útil cuando la ejecución de una promesa depende del resultado producido por la anterior, pero también promesas en paralelo, de manera que se realicen todas a una y ahorremos tiempo.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...