Cómo usar async/await

Usar promesas de forma aparentemente síncrona


Si ya conoces las promesas en Javascript y las bases del async/await, quizás te pueda interesar utilizar promesas mediante async/await, ya que es una sintaxis alternativa mucho más simple y clara de escribir.

La palabra clave await

La palabra clave await se coloca justo antes de una promesa para «esperar a que se resuelva». Vamos a modificar el fragmento de código anterior, para manejar la que devuelve el fetch(), pero en lugar de gestionarla con .then(), lo haremos con await.

const response = await fetch("/data.json");
const data = await response.json();
console.log(data);

console.log("Código síncrono.");

Lo que hace await es «pausar» la función. Se esperará a que se resuelva la promesa, y hasta que no lo haga, no continua. A diferencia del .then(), el código parece ser bloqueante, aunque tiene matices que explicaremos más adelante.

Ahora, vamos a introducir este fragmento de código dentro de una función llamada request(). Quedaría de la siguiente forma:

function request() {
  const response = await fetch("/data.json");
  const data = await response.json();
  return data;
}

const data = request();

Sin embargo, aquí tenemos un problema. Estamos utilizando await (asíncrono) dentro de una función request() (síncrono), por lo que antes de ejecutarla, al intentarla definir, nos aparecerá un error similar al siguiente:

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

Para evitar este error, debemos convertir la función request() en asíncrona.

La palabra clave async

Para poder utilizar el await dentro de nuestra función, tenemos que asegurarnos de que nuestra función es asíncrona, algo que se soluciona simplemente añadiéndole un async antes de definirla. En el caso de las arrow function, antes de los parámetros:

// Con funciones tradicionales
async function request() {
  const response = await fetch("/data.json");
  const data = await response.json();
  return data;
}

await request();
// Con funciones arrow (flecha)
const request = async () => {
  const response = await fetch("/data.json");
  const data = await response.json();
  return data;
}

await request();

Importante: Ten en cuenta que al añadir la palabra clave async a una función, la convertimos en asíncrona, por lo que a su vez, está devolviendo su propia promesa. Si quieres obtener el valor final, utiliza nuevamente await al llamar a request().

Promesas rechazadas con try/catch

En anteriores temas vimos que podíamos utilizar el método .catch() después de los .then() para gestionar cuando una promesa se rechazaba. Sin embargo, en el caso de querer controlar errores o promesas rechazadas con async/await, tendríamos que usar un bloque try/catch:

const request = async () => {
  try {
    const response = await fetch("/data.json");
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("Error al realizar la petición o procesar los datos: ", error);
  }
}

Todo el código en el interior del bloque try, estará siendo examinado, de modo que si ocurre un error o se rechaza una promesa, se interrumpe la ejecución y se salta al bloque catch.

De esta forma, los bloques try/catch se están encargando de controlar los errores o el rechazo de promesas, tal y como hacíamos con el método .catch() en los thennables.

Async/await + .then()

Es importante darse cuenta que no es necesario utilizar una misma estrategia siempre, sino que se pueden combinar. Al usar un fetch(), donde tenemos que manejar dos promesas encadenadas, es posible utilizar una estrategia hibrida donde utilicemos .then() para la primera promesa y await para la segunda, por ejemplo:

async function request() {
  return await fetch("/data.json")
    .then(response => response.json());
}

await request();
function request() {
  return fetch("/data.json")
    .then(async response => await response.json());
}

await request();

En este caso, observa que el fetch() devuelve una primera que es manejada por el .then(). La segunda , devuelta por el método response.json() se devuelve hacia fuera del fetch() y es manejada por el await, que espera a que se cumpla, y una vez resuelta, se devuelve como valor de la función request().

También se podría hacer a la inversa, como en el segundo ejemplo. En este caso, al final hacemos un await request() no porque la función sea estrictamente asíncrona (que no lo es), sino porque el fetch() devuelve una promesa, y por lo tanto usamos el await para esperar a que se cumpla.

Importante: Ciertos casos no permiten usar async/await, como por ejemplo el método .map() de un de . En esos casos, se recomienda usar un for..of o controlar las promesas.

¿Quién soy yo?

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