"El buen diseño es obvio. El gran diseño es transparente"
- Joe Sparano

Async/Await en Javascript

4 minutos de lectura
Fecha: 7/11/2021

Async/Await en Javascript es la manera de declarar funciones asíncronas y poder trabajar con ellas de forma síncrona.

Un ejemplo sería hacer un fetch de un fichero json. ¿Cuanto va a tardar en traerse ese fichero? Pues depende, tardara más si el fichero es muy grande o dependiendo de la velocidad de la red, o incluso puede que no este disponible y no puedas traértelo.

El caso es que el tiempo que tarda es indeterminado. Pero imagínate que necesitas tener los datos que hay en ese fichero para continuar con la ejecución del código.

El fetch va a ser asíncrono, es decir que va a ejecutarlo e inmediatamente va a seguir con la ejecución de la siguiente linea. Para poder esperarlo lo que tenemos que hacer es declarar que la función donde se encuentra es asíncrona, y que debemos esperar a que el fetch responda para poder seguir con la ejecución de código.

Con esto estamos haciendo que la ejecución de nuestro código sea síncrono, porque va a esperar por el resultado de esa parte que es asíncrona para continuar. Y eso lo hacemos con Async/Await.

Caso de uso práctico

Para entenderlo vamos a poner un fragmento de código donde yo me traigo un fichero que tiene unos settings que necesita la aplicación. Y que no tenemos hardcodeados en el código porque varían en función del entorno donde se ejecutan (por ejemplo).

async getSettings() {
    let clientsettings: any;
    try {
      const request = await fetch('settings.json');
      clientsettings = (await request.json()).settings;
    } catch (e) {
      console.error(e);
    }
    const baseUrl = clientsettings.apiBaseUrl;

    // MAS LÓGICA DE NUESTRA FUNCIÓN
}

Como veis declaramos la función asíncrona poniendo la palabra reservada async antes del nombre de la función.

async getSettings() {...

Y luego hay varios puntos donde obligamos a esperar a que nuestro código asíncrono responda con la palabra reservada await

const request = await fetch('settings.json');
clientsettings = (await request.json()).settings;

Al hacer esto podemos ejecutar el resto del código como si fuera síncrono, si no hubiéramos puesto el await, cuando llegáramos a esta linea de código:

const baseUrl = clientsettings.apiBaseUrl;

Devolvería un error porque la variable clientsettings no tendría el dato apiBaseUrl, porque intentaríamos acceder a ella antes de que se obtuvieran los datos. (De hecho ya fallaría antes porque sin el await no funcionarían las sentencias de arriba)

¿Cómo llamamos a una función asíncrona?

Un tema importante es como se llama a una función donde hemos empleado el Async/Await en Javascript.

Y este es un tema importante porque no puedes llamarla como si fuera una función común.

Este dato es uno de los más importantes que debes recordar para trabajar con Async/Await en Javascript. Las funciones asíncronas siempre devuelven una promesa. Entender esto es la clave para trabajar con ellas.

Aunque no es el tema del post y seguramente ya lo sepas. Una promesa es un objeto que representa un valor que estará disponible (o no) en el futuro (y no sabemos cuando). Las promesas por tanto estarán en un estado pendiente mientras esperan que ese valor este disponible, cuando lo esté pasará a resuelta, y si no lo está será rechazada.

De manera que podemos tratar las funciones asíncronas como si fueran promesas. Y ahora te preguntarás, ¿y en ese caso que has puesto arriba donde has resuelto la promesa? Pues vamos a llamarla como una promesa y lo vemos…

main() {
    getSettings().then((data) => {
        console.log(data); //DEVUELVE undefined
    }
}

Como no hemos resuelto de forma explicita la promesa podríamos pensar que se va a quedar colgada sin devolver nada. Pero no sucede así las funciones asíncronas cuando terminan su ejecución por defecto resuelven la promesa con un undefined.

Y como podríamos hacer si quisiéramos que al final de la ejecución se devolviera el contenido del fichero de settings y además controlar también si falla de una manera más explicita. Pues simplemente forzando nosotros la devolución de la promesa. Os pongo el código…

async getSettings() {
    let clientsettings: any;
    try {
      const request = await fetch('settings.json');
      clientsettings = (await request.json()).settings;
    } catch (e) {
      return Promise.reject();
    }
    return Promise.resolve(clientsettings);
}

main() {
    getSettings().then((data) => {
        console.log(data); //AHORA SI QUE DEVUELVE LOS DATOS
    }.catch(() => console.error('No se ha podido leer el fichero de settings'));
}

Ahora lo que estamos haciendo es forzar nosotros la devolución de los datos del fichero si los obtiene, o rechazar la promesa en caso de que no pueda conseguirlos.

Así que su uso es bastante flexible, la única norma que debes respetar es que si pones un return eso tiene que devolver una promesa.

Para terminar hay otra manera de llamar a estas funciones que sería volver a emplear el Async/Await.

async main() {
    await getSettings();
    // SEGUIR CON LA LÓGICA
}

Esto también funciona, así te aseguras que antes de seguir con la lógica de la función main, todo el código del getSettings se ha ejecutado (y ha esperado todo lo que tenia que esperar), pero ahora tienes el «problema» de que tu función también va a devolver una promesa. Por lo que al invocar la función main tendrás que tenerlo en cuenta.

Puede haber casos donde sea útil hacerlo de esta manera, pero hay que tener cuidado de no crear un infierno de Async/Await anidándolos por todos lados si realmente no es necesario.

En resumen, el uso de Async/Await en Javascript simplifica un montón la gestión de tareas asíncronas, siendo un wrapper de una promesa fácil de implementar y muy descriptivo. Sin duda son elementos del lenguaje que nos van a ayudar mucho y que no tienen casi complejidad entendiendo de antemano como funciona una promesa.