DEV Community

David Vargas
David Vargas

Posted on

Las promesas y async await lógicamente no son iguales. Y te explico el porqué

¿Alguna vez te has preguntado el por qué existe async await si ya se tiene las promesas? Muchos desarrolladores ya están usando esta funcionalidad. Sin embargo, no saben cómo funciona o cómo es diferente de las promesas. Eso es exactamente lo que abarcará este post. Empezaremos recordando por qué llegaron las promesas y cómo es que funciona, luego será el turno de async await y su funcionamiento. Finalmente veremos un ejemplo de cómo aplicar estas dos formas de manejar la asíncronia en conjunto.

  • ¿Por qué Promesas?
  • La llegada de Async Await
  • Async Await y Promesas trabajando juntos

Para poder entender varios términos que se va a usar, es necesario tener cierto conocimiento previo de Javascript y su asíncronia. Por eso, te recomiendo leer estos posts. Son muy buenos!

Ya tenemos todo lo necesario y ya estás listo para seguir leyendo este post. Esta es una oportunidad de entender mejor la asíncronia en Javascript. A por ello!

Alt Text

¿Por qué Promesas?

Las promesas llegan en ECMAscript 2016 como solución a uno de los problemas que generaba los callbacks, el callback hell. Este hacía que el código asíncronico se apile horizontalmente a la derecha. Por lo cual esto hacía que el código se vuelva muy complicado de leer. Lo que planteaba las promesas era una mejor sintaxis. En vez de que el código esté horizonal, pues que esté vertical y encadenado. Veamos un ejemplo para entenderlo mejor.

En estos trozos de código hacemos unas llamadas a un api para conseguir el id de un usuario, conseguir sus seguidores y haces más cosas. Haciendo esto con tan solo callbacks, nos resulta como en la imagen. Imagínate que haya muchos más callbacks. ¿Difícil de leer, no?

    callEndpoint('api/getidbyusername/davos', function (results) {
        callEndpoint('api/getfollowersbyid' + results.userId, function (results) {
            callEndpoint('api/someothercall', function (results) {
                callEndpoint('api/someothercall', function (results) {
                    callEndpoint('api/someothercall', function (results) {
                        callEndpoint('api/someothercall', function (results) {
                        // do things here
                        })
                    })
                })
            })
        })
    })
Enter fullscreen mode Exit fullscreen mode

Pero con las promesas todo es diferente, el código se vuelve vertical y más legible.

    callEndpoint('api/getidbyusername/davos')
        .then(results => callEndpoint('api/getfollowersbyid' + results.userId))
        .then(results => callEndpoint('api/someothercall'))
        .then(results => callEndpoint('api/someothercall'))
        .then(results => callEndpoint('api/someothercall'))
        .then(results => callEndpoint('api/someothercall'))
Enter fullscreen mode Exit fullscreen mode

Entonces, ¿Las promesas es solo sintaxis y funcionan igual que los callbacks?

No exactamente. Es cierto que las promesas manejan código que será ejecutado en algún futuro al igual que los callbacks. Nótese aquí la incertidumbre de cuándo será ejecutado este código. Sin embargo, la diferencia está en el mecanismo de las promesas. Pero antes, repasemos un poco para entender este mecanismo. El código síncrono inmediatamente se va a un lugar llamado el Call Stack, aquí la última función que entra al stack es el primero que se ejecuta y que sale del stack así hasta la primera que ingreso. Por otro lado, el asíncrono se va a una cola de tareas para su respectiva ejecución. Una vez que el Call Stack esté vacío, el Event Loop moverá las funciones que ya estén listas de la cola de tareas al Call Stack y luego pasarán a mostrar su resultado. Con esto en mente retomemos las promesas. Estas se dirigen a una cola de tareas diferente a las que van los callbacks. Los callbacks se van al Task Queue y las promesas al PromiseJobs o también llamado MicroTask Queue. Estos son manejadores de tareas, básicamente son los que deciden qué funciones son las que entran y las que salen.

https://miro.medium.com/max/771/1*CUyECMl99NBj7PurcEUwVg.jpeg

Referencia: https://medium.com/@jitubutwal144/javascript-how-is-callback-execution-strategy-for-promises-different-than-dom-events-callback-73c0e9e203b1

Si te has confundido o si aún así quieres saber un poco más de los Tasks, MicroTasks y queues, te dejo este post muy bueno para profundizar estos conceptos.

Tasks, microtasks, queues and schedules

Ahora ya tenemos una idea de cómo funciona las promesas. ¿Y async await? Pues vamos a ello.

La llegada de Async Await

En ECMAscript 2017 es cuando Async Await entra al juego. Este nuevo feature de Javascript planteaba un mejor manejo de las promesas. Estos ya no estarían encadenados uno del otro volviendo la sintaxis más entendible y fácil de usar. Sobre todo fácil de usar. Para usarlo tan solo se necesita async functions y la keyword await. Este keyword permite que una promesa se resuelva y retorne su valor, esto permite que podamos guardarlo en variables. Pero no todo podía ser oro. await solo funciona en async functions. Este tipo de funciones simplemente se aseguran que lo que sea que retornen sea una promesa. Dicho de otro modo, estas funciones siempre retornan una promesa. Veámoslo en un ejemplo.

Tomaremos el ejemplo de las promesas y convertiremos su sintaxis usando async await

    // usando Promesas
    callEndpoint('api/getidbyusername/davos')
        .then(results => callEndpoint('api/getfollowersbyid' + results.userId))
        .then(results => callEndpoint('api/someothercall'))
        .then(results => callEndpoint('api/someothercall'))
        .then(results => callEndpoint('api/someothercall'))
        .then(results => callEndpoint('api/someothercall'))

    // usando Async Await
    async function callEndpoints() {
        const userResults = await callEndpoint('api/getidbyusername/davos')
        const followersResults = await callEndpoint('api/getfollowersbyid' + userResults.userId)
        const someResults = await callEndpoint('api/someothercall')
        const moreResults = await callEndpoint('api/someothercall')
        const anotherResults = await callEndpoint('api/someothercall')
        const finalResults = await callEndpoint('api/someothercall')

        return finalResults
    }

    callEndpoints()
Enter fullscreen mode Exit fullscreen mode

Después de ver la sintaxis creo que estamos de acuerdo que es mucho más simple y entendible de usar. Sin embargo, el manejo de async await es diferente a la de las promesas. Sabemos que await hace una pausa hasta que la promesa se resuelva. Literalmente, hace que la ejecución del async function espere hasta que la promesa se resuelva y retorne un valor, aúnque esto no detiene el engine del lenguaje, este aún puede ejecutar otros scripts o eventos, esto significa que está volviendo el código asíncrono en síncrono. Y tal vez habrás pensado en qué sentido tiene esto si ya no va a ser asíncrono o no sea de utilidad y que mejor sigues trabajando con las promesas. Pues, esto no es totalmente cierto. Async Await puede brillar en ciertos casos de uso donde necesitemos esperar y saber cuándo alguna función asíncrona se ejecute, por ejemplo en el caso de pedidos a una api, donde necesitemos que primero la página se llene de datos para que el usuario pueda interactuar.

Pero, y si te dijera que podemos ir aún más allá y combinar lo mejor de ambos mundos. Podemos aprovechar la pausa de async await y las utilidades de las promesas como Promise.all . Esto lo veremos en el siguiente tema en un caso de uso donde sean necesario tener ambas.

Async Await y Promesas trabajando juntos

Vamos a suponer que estamos programando una carga inicial del perfil de usuario y que uno de los requerimientos sea que debamos mostrar la información básica del usuario, los cursos que ha tomado en la plataforma y la lista de sus amigos antes de que termine la carga. Estos recursos se consiguen por medio de una api, y cada recurso está en una diferente url. Y las url del api de los cursos y la de amigos vienen en la información del usuario en la propiedad links.

  • Información del usuario: api/user/1234
  • Cursos que ha tomado: api/user/1234/courses
  • Lista de sus amigos: api/user/1234/friends

Este es un ejemplo de la respuesta a un pedido a la url de la información del usuario

    {
        user: {
            id: 1234,
            ...
            links: ['api/user/1234/courses', 'api/user/1234/friends']
        }
    }
Enter fullscreen mode Exit fullscreen mode

Entonces tenemos que hacer 3 pedidos a la api y debemos tener acceso a su data antes de que termine la carga. Está claro lo que debemos usar, async await y promesas.

Vamos a crear una async function donde en primera instancia haremos un pedido a la url del usuario para obtener la información básica y los links que están como propiedad del usuario. Luego, usaremos una utilidad de las promesas, Promise.all. Esto hará que los pedidos se ejecuten paralelamente, por lo tanto el tiempo de espera se disminuye al no tener que ejectuar los pedidos de los links consecutivamente. Un detalle es que si alguno de estos pedidos falla en el Promise.all, todos fallarán. O todo o nada.

Dentro de Promise.all, iteraremos sobre los links con la función de los arreglos, map . Este recibe una función que tiene como argumento el elemento del arreglo en cada iteración, en este caso el link. Luego dentro de la función, aplicamos un arrow function que retorna un fetch al link en cada iteración. Esto hará que en cada iteración se retorne una promesa. Al final, tendremos un arreglo de estas promesas sin resolver. Para esto aplicamos await al Promise.all para que resuelva todas las promesas paralelamente. Una vez ya resueltos, obtendremos todas las respuestas de los pedidos si todo fue bien y lo guardamos en una variable userResponse. Por último aplicamos todo esto de nuevo para parsear las respuestas en data de tipo objeto para que Javascript pueda hacer operaciones sobre la data.

    async function getAllUserInfo(id) {
        const user = await fetch('api/user/' + id)

        const userResponse = await Promise.all(user.links.map(link => fetch(link)))

        const userData = await Promise.all(userResponse.map(response => response.json()))

        return userData
    }
Enter fullscreen mode Exit fullscreen mode

Por último obtenemos la data requerida para el usuario, y logramos hacer que la información se visualice por el usuario final.

Conclusión

Para resumir, las promesas y async await resuelven la asincronía de distinta forma. Con las promesas no sabemos cuándo se va a resolver y con async await forzamos una espera en la función. No siempre se va a usar uno, el otro o ambos, por ello lo primero es entender el caso de uso y después empezamos a implementar todo lo que hemos aprendido aquí.

Top comments (4)

Collapse
 
otipip90 profile image
otipip90

Muy bueno, gracias

Collapse
 
seba2020 profile image
Sebastian Hondarza G

Muy buen post, gracias !

Collapse
 
betoml5 profile image
Betoml5

Excelente post!

Collapse
 
oscarivanlopez profile image
Ivan Lopez

Excelente post, ¡gracias!