Async Await, JS assincronamente síncrono

O JavaScript tem evoluído muito nestes últimos anos e ainda temos muitas novidades interessantes a caminho!

Este artigo é a continuação de uma série onde estamos discutindo as características assíncronas do JavaScript. Confira a lista dos artigos anteriores.

As promises são uma daquelas coisas que trouxeram muitas mudanças na forma como trabalhamos com código assíncrono.

Agora, com Async/Await, nós vamos evoluir ainda mais o tema assíncrono.

Suporte

É chegada a hora de darmos mais um passo largo na especificação do ES8/ES2017 (atualmente em estágio 3). O time do Chrome anunciou suporte oficial ao async/await na versão 55 do navegador, pois já estão trabalhando na implementação das novidades do ES8/ES2017.

O Jaydson fez uma análise bem interessante sobre o histórico e o uso de async functions em um estudo de caso disponível em seu GitHub.

Especificação

As duas palavrinhas mágicas aqui são o async e o await, e a especificação do TC39 você pode ler aqui.
Uma função é definida como assíncrona quando ela contém o modificador async e deve, então, devolver uma promise.

Uma função somente pode esperar (await) por outra função assíncrona, caso ela também seja assíncrona (async).

Async

const getUser = async function (){  
  return new Promise((resolve, reject)=>{
    setTimeout(_=>{
      resolve({ name: "Felipe" });
    }, 2000);
  });
};

Neste caso, estamos retornando uma promise que será resolvida em dois segundos, mas poderíamos resolvê-la após alguma outra ação assíncrona.

Await

O modificador await instruirá a JavaScript Engine a esperar pela resolução da função assíncrona antes de dar continuidade ao fluxo atual.

async function getUserFullData(){  
  var userData = await getUser();
  var userAddress = await getUserAddr(userData);
  console.log(userData, userAddress);
}

getUserFullData();  

No exemplo acima, a última linha nos retornará uma promise com status pending, e esta promise somente será resolvida quando a última instrução da nossa função assíncrona (o console.log) for executada.

O que acontece é que, sempre que a engine encontrar o token await, ela irá interromper o fluxo e aguardar pela resolução daquela instrução (que trata-se de uma promise ou outra função assíncrona).

Exemplos de async/await

Uma implementação útil para isso seria a criação do famoso (querido por uns, odiado por outros) sleep - impossível de implementar no JavaScript até então.

Vejam!

async function sleep (forHowLong) {  
  function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  await timeout(forHowLong);
}

Agora podemos usar a função sleep de duas formas. Como uma promise propriamente dita:

sleep(2000)  
  .then(result=>console.log(result));

Ou como uma chamada em outra função assíncrona:

async function countFromThree () {  
  await sleep(0);
  console.log(3);
  await sleep(1000);
  console.log(2);
  await sleep(1000);
  console.log(1);
  await sleep(1000);
  console.log('DONE');
}

E então:

countFromThree();  

Usando Async/Await como um contador

Claro que esta implementação foi muito básica.

Que tal usarmos a API do Github para listarmos todos os meus repositórios?

async function gitHub () {  
  const userName = 'felipenmoura';
  const url = 'https://api.github.com/users';
  const reposResponse = await fetch(`${url}/${userName}/repos`);
  const userRepos = await reposResponse.json();
  console.log(userRepos) 
}

Notem que a chamada assíncrona ao fetch ficará esperando pela sua resolução (await). Assim que a promise de fetch for resolvida, a constante userRepos esperará pela resolução da promise de reposResponse.json(), para somente então, partirmos para a instrução final, o console.log.

Exemplo BrazilJS

Vamos a um exemplo um pouco mais complexo e completo utilizando a API da BrazilJS:

// uma função async (retornará uma promise)
async function getBrazilJSLineup () {  
  // esperamos a lista de eventos chegar
  // e então esperamos seu body ser lido como JSON
  const listOfEvents = await (await fetch('https://braziljs.org/api/list/events')).json();
  // agora pegamos a ID da BrazilJS deste ano
  const BrazilJSID = listOfEvents.events.find(event=>event.name == 'BrazilJS Conf').id;
  // esperamos pelo resultado da API para esta ID
  let lineup = await fetch(`https://braziljs.org/api/list/lineup/${BrazilJSID}`);
  // e então retornamos uma nova espera, para a
  // leitura do body desta request no formato JSON
  return await lineup.json();
}

// aqui, nós chamamos a função assíncrona
// e esperamos pelo resultado ou por algum erro
getBrazilJSLineup()  
  .then(result=>console.log(result))
  .catch(reason=>console.warn(reason));

Importante

É importante ressaltar que, apesar de assíncrono, seu código será serial, ou seja, uma instrução (mesmo que assíncrona) será executada após a outra.
Muitas vezes, utilizamos chamadas assíncronas paralelamente, e este não é o caso aqui. Todavia, como funções assíncronas que se utilizem do async tornam-se promises, podemos usá-las em um fluxo tal qual usaríamos qualquer promise.

Uma coisa interessante é que podemos esperar pelo Promise.all também. Neste caso, podemos ter algumas promises em paralelo:

async function getInParallel () {  
  let promises = [];
  promises.push(getImages());
  promises.push(getJSONs());
  promises.push(getData());

  let [imgs, jsons, data] = await Promise.all(promises);
}

Neste exemplo, as três promises (getImages, getJSONs e getData) serão iniciadas simultânea e paralelamente, mas esperaremos por todas elas (await Promise.all).
No final disso, teremos três novas variáveis (imgs, jsons e data) recebendo os respectivos resultados.

Claro, vale enfatizar que sair por aí usando Promise.all para tudo não é uma boa ideia!
Para entender melhor como o Promise.all funciona, faça o seguinte: execute todas estas promises. Quando todas funcionarem, me avise. Caso uma falhar, abortamos todas.

Mas essa não é a solução para todas as situações. Por exemplo, podemos querer fazer um tratamento ou, até mesmo, um fallback diferente para cada promise que falhar.

Lidando com Erros

Com o uso do await, temos código assíncrono sendo executado de forma procedural e, portanto, podemos capturar erros que tenham ocorrido nele utilizando try/catch.
Sim, try/catch para suas funções assíncronas.

Mas, mais uma vantagem aqui é que, como as funções async se tornam promises, podemos escutar também por seu .catch. Temos as duas opções. Agora, você não tem desculpas para não capturar problemas nos teus algoritmos!

Suporte (usando hoje)

O suporte ao async/await ainda está sendo implementado, mas o time do Chromium anunciou semana passada o suporte no Chrome 55! Yay!

Já para transpilarmos nosso código a fim de usar async/await, podemos utilizar o Babel com o plugin transform-async-to-generator.

Temos também o Async to Gen que é bem leve e transpilará suas funções async em generators.

Então, estamos vivendo uma fase excitante do JavaScript ou não?!

fechar