JavaScript assíncrono moderno com Async e Await
Introdução
O JavaScript evoluiu de callbacks para promises (ES2015) em um curto período de tempo, e desde o ES2017, JavaScript assíncrono está ainda mais simples com a sintaxe async/await.
Funções async são uma combinação de promises e generators, e basicamente, elas são uma abstração de alto nível das promises. Permita-me repetir: async/await é feito com promises.
Por que a sintaxe async/await foi criada?
Ela reduz o amontoado de código em torno das prommises e a limitação de "não quebre a corrente" das promises encadeadas.
Quando as Promises foram introduzidas no ES2016, elas foram feitas para resolver um problema com código assíncrono, e elas fizeram isso, mas ao longos dos 2 anos que separaram o ES2015 do ES2017, ficou claro que promises poderiam não ser a solução definitiva.
Promises foram introduzidas para resolver o famoso problema de callback hell, mas elas introduziram complexidade por si só, e complexidade sintática.
Elas foram boas primitivas enquanto uma sintaxe melhor não surgia, então, quando chegou a hora certa, obtivemos as funções async.
Elas fazem o código parecer síncrono, mas por baixo dos panos ele é assíncrono e não bloqueante.
Como isso funciona
Uma função async retorna uma promise, como nesse exemplo:
const doSomethingAsync = () => {return new Promise(resolve => {setTimeout(() => resolve('I did something'), 3000)})}
Quando você quer chamar essa função você prexifa um await
, e o código corrente vai parar até que a promise seja resolvida ou rejeitada. Uma ressalva: a função deve ser definida como async
. Segue um exemplo:
const doSomething = async () => {console.log(await doSomethingAsync())}
Um exemplo rápido
Esse é um exemplo bem simples de async/await sendo usado para rodar uma função assíncronamente:
"Promissifique" tudo
Prefixar o termo async
em qualquer função implica que a função vai retornar uma promise.
Mesmo que isso não seja feito tão explicitamente, internamente fará com que retorne uma promise.
Por isso que esse código é válido:
const aFunction = async () => {return 'test'}aFunction().then(alert) // Isso vai invocar um alert com o texto 'test'
e o mesmo aqui:
const aFunction = async () => {return Promise.resolve('test')}aFunction().then(alert) // Isso vai invocar um alert com o texto 'test'
O código é muito mais simples de ler
Como você pôde ver no exemplo acima, nosso código parece muito simples. Compare-o com os códigos usando promises simples, encadeamento e funções callback.
E esse é um exemplo bem simples, os maiores benefícios vão aparecer quando o código for mais complexo.
Por exemplo, aqui temos um código de como obter um recurso JSON e "parsear", usando promises:
const getFirstUserData = () => {return fetch('/users.json') // pega a lista de users.then(response => response.json()) // parse JSON.then(users => users[0]) // pega o primeiro usuário.then(user => fetch(`/users/${user.name}`)) // pega os dados desse usuário.then(userResponse => userResponse.json()) // parse JSON}getFirstUserData()
E aqui temos a mesma funcionalidade usando async/await:
const getFirstUserData = async () => {const response = await fetch('/users.json') // pega a lista de usersconst users = await response.json() // parse JSONconst user = users[0] // pega o primeiro usuárioconst userResponse = await fetch(`/users/${user.name}`) // pega os dados desse usuárioconst userData = await userResponse.json() // parse JSONreturn userData}getFirstUserData()
Múltiplas funções async em série
Funções async podem ser encadeadas facilmente, e a sintaxe é muito mais legível do que usando promises simples:
Mais fácil de debugar
É difícil debugar promises porque o debugger não passa por cima de código assíncrono.
Já Async/await facilita muito isso porque para o compilador é como se fosse apenas código síncrono.