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][9]
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?!