Guia Prático: Funções Assíncronas no JavaScript
Funções Assíncronas
Funções assíncronas são funções que permitem que outros código sejam executados enquanto a função aguarda a finalização de algum processo externo, como chamadas de API, consulta ao banco de dados, processos em background, entre outros.
Por exemplo, quando você realiza alguma chamada para uma API, durante o tempo que o servidor demora pra responder outros códigos podem ser executado sem bloquear o segmento principal, assim o tempo que os processos são executados tornam-se mais rápido já que é aproveitado o tempo em que o processador ficará ocioso para executar outros processos.
Promises
No JavaScript funções assíncronas são feitas por meio de Promises
.
No nosso exemplo iremos simular uma chamada da API que irá levar 3 segundos para ser concluída, confira no código abaixo:
function consultarApi() {
return new Promise((*resolve*) => {
setTimeout(() => {
console.log('API Respondeu')
resolve()
}, 3000)
})
}
function main() {
console.log('Início')
consultarApi()
console.log('Fim')
}
main()
// Início
// Fim
// API Respondeu
.then(), .catch() .finally()
Como pode ver a resposta da API veio depois do fim da função main()
, isto porque como a função consultarApi()
é assíncrona, ou seja ela retorna uma Promise, o JavaScript não espera a finalização da Promise para continuar a execução do código. Mas isso pode ser um problema dependendo do caso, por exemplo, digamos que você esteja consultando a API para retornar os dados de um cliente, você vai querer esperar o retorno para realizar alguma tarefa, para isso podemos utilizar o .then():
function consultarApi() {
return new Promise((*resolve*) => {
setTimeout(() => {
console.log('API Respondeu')
let cliente = { nome: 'Michael Scott', empresa: 'Dunder Mifflin' }
resolve(cliente)
}, 3000)
})
}
function main() {
console.log('Início')
consultarApi()
.then((cliente) => {
console.log(cliente)
})
.catch((erro) => {
console.log(erro)
})
.finally(() => {
console.log('Fim')
})
}
main()
// Início
// API Respondeu
// { nome: 'Michael Scott', empresa: 'Dunder Mifflin' }
// Fim
Agora o código foi executado na ordem em que planejamos, a função .then()
é chamada assim que a Promise é finalizada com sucesso.
Chamadas de API, por exemplo, podem gerar erros, como parâmetros errados ou perda de conexão, por isso eles devem ser tratados utilizando o .catch()
que é executado caso ocorra algum erro dentro da Promise.
O finally()
é executado após as funções .then()
e .catch()
, com a Promise dando erro ou não.
ASync e Await
Outra forma de esperar a finalização das Promises é utilizando Async e Await. Imagine se precisarmos encadear 5 chamada para a API para só assim usarmos o resultado final, utilizando o .then()
ficaria algo assim:
function main() {
console.log('Início')
consultarApi()
.then((*cliente1*) => {
consultarApi().then((*cliente2*) => {
consultarApi().then((*cliente3*) => {
consultarApi().then((*cliente4*) => {
consultarApi().then((*cliente5*) => {
console.log('Fazer algo com o resultado')
})
})
})
})
})
.catch((*erro*) => {
console.log(erro)
})
.finally(() => {
console.log('Fim')
})
}
O código não fica muito limpo, né? Isso é conhecido como Callback Hell.
Para deixar o código mais limpo e legível nesse caso utilizamos o Async e Await, o código ficaria assim:
async function main() {
try {
let cliente1 = await consultarApi()
let cliente2 = await consultarApi()
let cliente3 = await consultarApi()
let cliente4 = await consultarApi()
let cliente5 = await consultarApi()
console.log("Fazer algo com o resultado")
} catch (error) {
console.log(error)
}
}
Para conseguir utilizar o await
, obrigatoriamente a função deve ser declarada como async
.
Promise.All()
Como vimos no exemplo anterior com o await
conseguimos esperar o retorno da API para executarmos o código apenas depois da finalização da Promise, mas como no caso acima não precisamos do cliente1 para buscar as informações do cliente2, assim como no caso das chamadas seguintes, poderíamos ter realizados todas as consultas de uma só vez e esperar para que todas elas sejam resolvidas para continuar, desse modo a execução seria muito mais rápida, para isso utilizamos o Promise.All()
.
Como cada função consultarApi()
leva 3 segundos para resolver, no exemplo anterior e elas estão encadeadas a função main()
leva 15 segundos para ser finalizada, utilizando o Promise.All()
o tempo de execução será igual a consulta que leva mais tempo para ser concluída, como no nosso exemplo todas levam 3 segundos, então o tempo total de execução seria de 3 segundos, confira o exemplo abaixo:
async function main() {
try {
let resultado = await Promise.all([
consultarApi(),
consultarApi(),
consultarApi(),
consultarApi(),
consultarApi(),
])
let [cliente1, cliente2, cliente3, cliente4, cliente5] = resultado
console.log("Fazer algo com o resultado")
} catch (error) {
console.log(error)
} finally {
console.log("Fim")
}
}
A função Promise.All()
recebe como parâmetro um array de Promise, lembre-se que a função consultarApi()
não retorna um cliente e sim uma Promise que só quando resolvida com await ou .then() retorna o cliente. O retorno da função é uma Promise também, que quando resolvida retorna um array com as respostas de cada chamada de acordo com o índice do array, por exemplo, a resposta da primeira chamada que foi passado para a Promise.All()
vai ser o índice 0 da variável resultado.
Conclusão
Utilizar funções assíncronas da maneira correta podem dar mais performance a sua aplicação e no JavaScript saber usar as duas formas, com async/await ou .then(), pode também ajudar na clareza do código escrito. Se atente também ao uso do Promise.All()
para não esperar a resolução de Promises nos momentos errados.