¡Hola! Me parece que andas usando un bloqueador de anuncios =( ¿Nos desbloqueas? ¿Por qué?
F13

Funcion 13

Comprende async y await en JavaScript

Esta semana apareció la versión 7.6 de Node.js que, entre otras cosas, nos permite el uso de async y await sin librerías, ni flags, ni nada de nada. A pelo. Nativo. Increíble.

Estas forman parte de ES7, que aunque aun está en borrador, ya es estable en Node.

Veamos un poco de terminología para entender esto mejor:

  • Función async (asíncrona)
  • Palabra clave await

Lo pongo así, separado, para ver las diferencias porque es habitual leer async/await en tándem pero no son lo mismo aunque, ciertamente, están relacionadas.

Las funciones async

Cuando creas una función con la palabra clave async, la función nos va a devolver siempre una Promesa. Cuando devuelves algo en la función, lo encapsula con una promesa. Veamos un ejemplo:

async function dameUnNumero() {  
  return 13;
}

// La misma función pero usando promesas
function dameUnNumero() {  
  return Promise.resolve(4);
}

Conveniente, ¿no? Básicamente es una forma sencilla de quitarnos de complicaciones de tener que incluir Promise y además estamos declarando de manera muy explícita la naturaleza asíncrona de la función.

Nota: Quizá te estés preguntando qué son las promesas. Te tengo las espaldas cubiertas con Aprende ES6 - Las promesas. ¡De nada!

Usando await... dentro de async

Además de convertir el valor devuelto en Promesa, una función asíncrona es también especial dado que es el único sitio (por el momento) en el que puedes usar la palabra clave await.

La palabra clave await nos permite pausar la ejecución de una función asíncrona hasta que reciba el resultado de una promesa. Esto nos permite escribir código asíncrono que se lea en el orden en el que se ejecuta y sin una maraña de callbacks o .then.

Veamos un ejemplo:

async function muestraNombrePokemon(id) {  
  let pokemon = await fetch(
    `https://pokeapi.co/api/v2/pokemon/${id}/`
  );
  console.log(pokemon.name);
}

muestraNombrePokemon(4); // charmander

// Equivalente con promesas
function muestraNombrePokemon(id) {  
  return fetch(
    `https://pokeapi.co/api/v2/pokemon/${id}/`
  ).then(pokemon => {
    console.log(pokemon.name);
  });
}

Nota: En este ejemplo hacemos uso de fetch. ¿No te suena? ¿No sabes lo que es? Entonces te recomiendo leer este artículo: El API fetch de JavaScript.

¿Observas la diferencia? await nos permite escribir código asíncrono sin necesidad de usar callbacks. Esto hace que el código sea mucho más legible. Lo bueno es que await funciona con cualquier promesa, no solo con promesas creadas por funciones asíncronas.

Gestión de errores en funciones asíncronas

Como ya hemos visto, una función que esté precedida por async es una Promesa. Si ocurre un error, lo que ocurriría es que la promesa sería rechazada.

async function muestraNombrePokemon(id) {  
  if (id < 0) {
    throw new Error('Este ID es erróneo');
  }
}

// Equivalente usando promesas
function muestraNombrePokemon(id) {  
  if (id < 0) {
    return Promise.reject(new Error('Este ID es erróneo'));
  }
}

En el caso de que estemos usando await, podemos usar un bloque try/catch para poder cazar el error dentro de la función:

async function muestraNombrePokemon(id) {  
  try {
    let pokemon = await fetch(
      `https://pokeapi.co/api/v2/pokemon/${id}/`
    );

    console.log(pokemon.name);
  } catch (err) {
    console.error(err);
  }
}

muestraNombrePokemon(4); // charmander

// Equivalente con promesas
function muestraNombrePokemon(id) {  
  return fetch(
    `https://pokeapi.co/api/v2/pokemon/${id}/`
  ).then(pokemon => {
    console.log(pokemon.name);
  }).catch(err => {
    console.error(err);
  });
}

Concurrencia en async

Hay casos en los que querremos que varias llamadas se ejecuten a la vez, pero si estamos usando await siempre, el código se vuelve síncrono. Veamos un ejemplo para ilustrar lo que quiero decir:

function muestraNombrePokemon(id) {  
  return fetch(`https://pokeapi.co/api/v2/pokemon/${id}/`);
}

async function muestraNombrePokemons() {  
  const charmander = await muestraNombrePokemon(4);

  // No empezaremos a obtener los datos de charmelon
  // hasta que los de charmander se hayan recibido
  const charmeleon = await muestraNombrePokemon(5);

  // No empezaremos a obtener los datos de charizard
  // hasta que los de charmeleon y charmander se hayan 
  // recibido
  const charizard = await muestraNombrePokemon(6);
}

La ejecución iría primero y solicitaría los datos del primer pokemon. Pero todo se detendría hasta que los datos no hubieran sido recibidos. Esto al final resulta en un proceso lento si los datos no dependen unos de otros.

Veamos cómo arreglarlo:

function muestraNombrePokemon(id) {  
  return fetch(`https://pokeapi.co/api/v2/pokemon/${id}/`);
}

async function muestraNombrePokemons() {  
  const charmanderPromesa = muestraNombrePokemon(4);
  const charmeleonPromesa = muestraNombrePokemon(5);
  const charizardPromesa = muestraNombrePokemon(6);

  const [
    charmander,
    charmeleon,
    charizard
  ] = await Promise.all([
    charmanderPromesa,
    charmeleonPromesa,
    charizardPromesa
  ]);
}

Lo que hacemos es quitar await de cada promesa para hacer que se ejecuten de manera concurrente y las agrupamos todas con Promise.all al que si prefijamos con await.

Nota: Dado que Promise.all al ser resuelto devuelve una matriz con todos los valores, podemos hacer uso de la desestructuración de ES6 para obtener variables independientes. Si has visto esa sintaxis y te ha extrañado, te recomiendo que leas Aprende ES6 - Desestructurando.

¡Buen truco! ¿No te parece?

Programador Front-end en First + Third y Potato. Trabajando con JavaScript y HTML5 desde el corazón de Sevilla.

Comentarios ¡Únete a la conversación!