JavaScript Asíncrono: La Guía Completa - Parte 5 - Entendiendo Async/Await

Ronaldo B.1 de agosto de 20228 min de lectura

En este artículo

En este artículo vamos a hablar sobre async/await. Si lo deseas, puedes consultar las otras publicaciones de esta serie en estos enlaces:

  1. JavaScript Asíncrono: La Guía Completa - Parte 1 - ¿Qué es un código asíncrono?
  2. JavaScript Asíncrono: La Guía Completa - Parte 2 - Callbacks
  3. JavaScript Asíncrono: La Guía Completa - Parte 3 - Entrenando Callbacks
  4. JavaScript Asíncrono: La Guía Completa - Parte 4 - Usando Promises

Aprendimos en el último artículo sobre las promises, un recurso muy útil que JavaScript nos proporciona para códigos asíncronos. Cuando las promises fueron lanzadas, quizás pensáramos que ya estaba excelente: nos ayudan a crear un código organizado, legible, fácil de entender y desarrollar. Quizás pensáramos que no había manera de mejorar. Pero ¡sí la había! En junio de 2017 se lanzó una nueva versión de EcmaScript, la versión ES2017 o ES8. Esta versión introdujo un nuevo concepto en el lenguaje JavaScript: funciones asíncronas, o async/await. El objetivo de estas funciones es facilitar aún más el uso de promises. Una función asíncrona consiste en crear una función “normal”, pero que ganará como que “superpoderes”, convirtiéndose en una función capaz de trabajar con código asíncrono. ¿Cómo es esto posible? Podemos empezar analizando lo que las palabras async y await representan.

Async/Await

1 - Async. Esta palabra puede ser utilizada al crear una función convencional. Cuando añadimos este identificador en la creación de esta función, definimos que será una función asíncrona, y lo mejor, ¡retornará una promesa! Cuando usemos la expresión return estaremos, en realidad, resolviendo una promesa.

2 - Await. Esta palabra será utilizada con el objetivo de esperar la resolución de una función asíncrona. Si hay una serie de funciones asíncronas, la expresión await definirá que el código solo continuará cuando la función anterior sea resuelta. Un detalle muy importante: la expresión await solo será aceptada en una función que ya sea asíncrona, es decir, que tenga el identificador async al inicio.

Desarrollemos un primer ejemplo de este concepto. Vamos a crear una función que unirá dos strings. Esta función será asíncrona. Luego, llamaremos a esta función usando el identificador await. Véase el script a continuación:

async function joinStrings(string1, string2) {
    return string1 + ' ' + string2;
}
async function init() {
    await joinStrings('Hcode', 'Treinamentos').then(result => {;
        console.log(result);
    });
}
init();

Explicando: la función joinStrings() realizará la concatenación de dos strings; fue creada con el identificador async, dejando claro que es una función asíncrona. La función init() realizará la llamada a esta función, pero con el identificador await. Además, nota que usamos el método .then() después de llamar a la función, como hacemos en una promesa. Esto es posible porque la función joinStrings() es de hecho una promise, pero no necesitamos crear toda su estructura. Solo necesitamos añadir la palabra async en su creación. ¡Genial, ¿no?!

Este fue un primer ejemplo y ya fue posible notar que las funciones asíncronas son muy útiles. Pero quizás podamos darnos cuenta de esto aún mejor cuando las usamos para sustituir una serie de promesas. Veamos un ejemplo de esto.

Vamos a crear tres funciones que manipularán una string: 1) una que convertirá la primera letra de la string a mayúscula; 2) una que invertirá el contenido de la string y 3) una que añadirá un texto a esta string. Todas estas funciones serán promesas. Aquí está el código de creación de ellas:

function firstLetterToUpper(string) {
    return new Promise((resolve, reject) => {
        let firstLetter = string.charAt(0);
        let newString = firstLetter.toUpperCase() + string.slice(1);
        resolve(newString);
    });
}

function reverseString(string) {
    return new Promise((resolve, reject) => {
        let stringReverse = string.split('').reverse().join('');
        resolve(stringReverse);
    });
}

function addHelloToString(string) {
    return new Promise((resolve, reject) => {
        let newString = string + ' - Hello World';
        resolve(newString);
    });

}

Perfecto. Nuestras funciones fueron creadas y son bien simples de entender. Vamos ahora a llamarlas. Pero, haremos algo diferente: vamos a llamarlas una tras otra, informando como parámetro para cada una el valor retornado por la promesa anterior. Además, realizaremos un console.log() en cada valor retornado. El código para la solicitud de estas funciones es el siguiente:

firstLetterToUpper('hcode')
    .then(newValue => {
        console.log(newValue);
        reverseString(newValue)
            .then(reverseValue => {
                console.log(reverseValue);
                addHelloToString(reverseValue)
                    .then(helloString => {
                        console.log(helloString);
                    })
            })
    });

Al ejecutar este código, vemos el siguiente resultado:

Resultado de la función en la consola

Muy bien, funcionó sin problemas. Sin embargo, observa que la llamada de varias promesas en secuencia quedó un poco difícil de leer y entender. Parece un poco esa pirámide de callbacks que hablamos en el artículo anterior. Además, usamos el método .then() varias veces, lo que hace que el código sea un tanto repetitivo. Vamos a convertir este código a funciones asíncronas y ver el mismo resultado usando este nuevo concepto.

Primero, las funciones ya no devolverán promesas, sino que tendrán el identificador async al inicio. Además, vamos a sustituir el método resolve() por return. El código de las funciones quedará así:

async function firstLetterToUpper(string) {
    let firstLetter = string.charAt(0);
    let newString = firstLetter.toUpperCase() + string.slice(1);
    return newString;
}

async function reverseString(string) {
    let stringReverse = string.split('').reverse().join('');
    return stringReverse;
}

async function addHelloToString(string) {
    let newString = string + ' - Hello World';
    return newString;
}

El código ya quedó mucho más limpio. Ahora vamos a realizar la llamada de cada función y ejecutar el console.log(), pero utilizando el await. El código quedará así:

let upperValue = await firstLetterToUpper('hcode');
console.log(upperValue);

let reverseValue = await reverseString(upperValue);
console.log(reverseValue);

let helloValue = await addHelloToString(reverseValue);
console.log(helloValue);

Nota que hemos añadido el valor retornado de cada función en una variable, otro recurso interesante que podemos usar cuando trabajamos con async/await. Además, como una función se llama debajo de la otra, de manera independiente, parece que nuestro código es síncrono, más intuitivo, fácil de entender, pero “bajo el capó” está ocurriendo un código asíncrono.

Antes de ejecutar este código, necesitamos hacer solo una cosa más: ¿recuerdas que dijimos que el await solo funciona en una función asíncrona? Entonces, para evitar errores, vamos a añadir la llamada de las funciones en una función principal, que podemos llamar callFunctions(). El código final quedará así:

async function callFunctions() {

    let upperValue = await firstLetterToUpper('hcode');
    console.log(upperValue);

    let reverseValue = await reverseString(upperValue);
    console.log(reverseValue);

    let helloValue = await addHelloToString(reverseValue);
    console.log(helloValue);

}
callFunctions();

Si ejecutamos este código, obtendremos el mismo resultado:

Resultado da função no console

¡Excelente! Funcionó de la misma manera, pero nuestro código quedó más limpio, organizado, fácil de comprender. Estas son las ventajas de usar funciones asíncronas.

Tratando Errores

Sabemos que las promesas recuperan los errores con el método .catch(). Pero, ¿y las funciones asíncronas, cómo lo hacen? Tenemos a nuestra disposición este mismo método, ya que las funciones asíncronas devuelven promesas, así que podemos acceder a los mismos recursos.

En lugar de usar el método reject() en una función asíncrona, podemos simplemente usar el código throw new Error(). Luego, en la respuesta de la función, usar el método .catch() o usar un bloque de try / catch. Así tenemos dos opciones para usar.

Ahora que entendemos el concepto de funciones asíncronas, vamos a implementarlas en nuestro proyecto de Hcode Games.

Practicando

Vamos a alterar primero el archivo requests.js. Actualmente, las funciones de este archivo están devolviendo promesas y trabajando con la API de Fetch, que también devuelve una promesa. Vamos a adaptar ambas para async/await. La función getGameByName() quedará así:

async function getGameByName(name) {
    try {

        let result = await fetch(`https://api.rawg.io/api/games?search=${name}`);

        result = await result.json();

        return result;

    } catch (error) {
        console.error(err);
    }
}

Vamos a entender los cambios juntos. Hemos quitado la declaración de return new Promise() y estamos utilizando un bloque de try/catch para realizar las operaciones en la función. Además, observa que usamos await en la llamada de la función fetch(), ya que devuelve una promesa. Además, usamos await en la línea debajo de ella también, puesto que el método .json() es una promesa de la misma manera. Por último, mira que el código se simplificó mucho con estos cambios. Vamos a hacer lo mismo para la función getRelatedGamesByName(). Aquí está su código:

async function getRelatedGamesByName(name) {
    try {

        let result = await fetch(`https://api.rawg.io/api/games/${name}/suggested`);

        result = await result.json();

        return result;

    } catch (err) {
        console.error(err);
    }
}

Genial. Podemos proceder al archivo HTML. Ahora, al llamar a las funciones, no usaremos más el método .then(). En su lugar, almacenaremos el valor devuelto por la función en una variable y continuaremos el código debajo de ella normalmente, como si fuera un código síncrono. Para esto, necesitaremos cambiar la función initGames() a una función asíncrona. Su código quedará así:

async function initGames(gamename) {
    setGameLoad(gamesListPrincipal);
    gamesListRelationed.innerHTML = '';
    let games = await getGameByName(gamename);
    gamesListPrincipal.innerHTML = '';
    games.results.forEach(game => {
        let divGame = setGameHTML(game);
        divGame.addEventListener('click', async e => {
            setGameLoad(gamesListRelationed);
            let gameTag = e.currentTarget;
            let gamename = gameTag.dataset.gamename;
            let gamesRelationed = await getRelatedGamesByName(gamename);
            gamesListRelationed.innerHTML = '';
            gamesRelationed.results.forEach(game => {

                let divGameRelationed = setGameHTML(game);

                gamesListRelationed.append(divGameRelationed);

            });

        });

        gamesListPrincipal.append(divGame);

    });

}

Nuestra función ahora es asíncrona y está esperando el valor devuelto por las promesas. Solo un detalle importante: nota que fue necesario añadir el identificador async en el método addEventListener(), ya que este método crea una nueva función, y dentro de ella usamos await.

Después de estas modificaciones, vamos a probar nuestro proyecto (guardamos el mejor juego para el final):

Lista de juegos de Hcode Games

¡Y nuestro proyecto está funcionando sin problemas! Muy bien, logramos adaptar nuestro sitio para async/await y ahora tenemos un proyecto que ha sido moldeado en varios conceptos de código asíncrono: callbacks, promises y por fin funciones asíncronas. Puedes acceder a todos los códigos desarrollados en esta serie en este enlace: https://github.com/hcodebr/javascript-assincrono-blog.

Hemos llegado al final de nuestra jornada por JavaScript asíncrono. Con el conocimiento que adquirimos juntos en estos artículos, ahora estamos preparados para decidir cuándo usaremos cada uno de estos recursos en las situaciones que podamos encontrar durante nuestro desarrollo. Esperamos que hayan disfrutado de esta serie y que les ayude en su aprendizaje de JavaScript.

¿Tienes algún otro tema que te gustaría ver aquí en nuestro Blog? Puedes enviar tus sugerencias clic aquí.

¡Muchas gracias por leer este artículo! Nos vemos en la próxima publicación :)

Sigue aprendiendo con Hcode

Regístrate en la plataforma de cursos y sigue las próximas guías del blog.