Javascript Assíncrono
O que é?
Um código é assíncrono quando implementa uma função que começa agora e termina depois, e não espera pelo resultado dessa função. Normalmente, o código é síncrono: isso significa que cada instrução tem que esperar a última ser concluída antes de prosseguir.
Exemplo de código síncrono
console.log(1)
console.log(2)
console.log(3)
console.log(4)
O que esse código imprime é:
1
2
3
4
Podemos ver que o output segue a mesma ordem do que escrevemos no exemplo..
Exemplo de código assíncrono
console.log(1)
console.log(2)
setTimeout(() => {
console.log('callback');
}, 2000);
console.log(3)
console.log(4)
O output do console é:
1
2
3
4
callback
Diferentemente do que esperávamos, o programa não espera o retorno do timeout para printar callback
. Ele continua o fluxo de execução do programa até receber a resposta da função callback, e então, printar.
Callback
O retorno de chamada é uma função disparada quando e se você obtiver o resultado de retorno. No exemplo abaixo, a função de retorno de chamada é acionada após 2 segundos, portanto, é acionada quando expirou o tempo limite (não se, não para de contar, exceto se você fechar a página do navegador ou se o mundo parar) :
setTimeout(() => {
console.log('callback');
}, 2000);
Callback Hell
E se você quiser definir diferentes tempos limite para cada saída? Não sei por que você faria isso, mas você pode pensar em um código como este:
setTimeout(() => {
console.log(1);
}, 3000);
setTimeout(() => {
console.log(2);
}, 2000);
setTimeout(() => {
console.log(3);
}, 1000);
O output seria:
3
2
1
Não! Mas não é o que você queria. Você deseja que ele espere 3 segundos e depois exiba 1
, espere 2 segundos e depois exiba 2
e, finalmente, espere 1 segundo e exiba 3
. Para fazer isso, você precisa aninhar os retornos de chamada:
setTimeout(() => {
console.log(1);
setTimeout(() => {
console.log(2);
setTimeout(() => {
console.log(3);
}, 1000);
}, 2000);
}, 3000);
Para esse código, o output é como as expectativas:
1
2
3
Mas eu espero que vc concorde que esse código é muito difícil de ler e entender. É por isso que esses callbacks aninhados são chamados de callback hell.
Promises
Uma Promise é uma promessa de que algo que você pediu vai acontecer. Isso significa que você não tem certeza se uma tarefa vai ser concluída ou não. Note que o conceito original e o conceito em javascript são muito parecidos.
Vamos criar uma situação:
Você é um programador (e se você está aqui, eu aposto que é isso mesmo), e você não sabe Promises. Eu estou te prometendo que você vai entender assim que terminar de ler.
Isso é uma promessa, e tem 3 estados:
- Pendente: Você não sabe se você vai aprender Promises
- Realizada: Eu estou orgulhosa de você e eu vou te dar um biscoito
- Rejeitada: Eu só menti
Escrevendo isso em Javascript:
let mentirosa = true;
let vaiAprenderPromises = new Promise(
(resolve, reject) => {
if(mentirosa){
let motivo = new Error('sim, eu menti');
reject(motivo);
}else{
let biscoito = "Estou te dando um biscoito!";
resolve(biscoito)
}
}
)
Eu acredito que esse código seja bem simples de entender. Mas, como formalidade, vou explicar a estrutura de uma Promise
.
Uma Promise
pega como um parâmetro uma função, e nessa função, nós automaticaticamente ganhamos acesso a 2 parâmetros: resolve
e reject
.
O parâmetro resolve
é utilizado para mostrar que o que nós esperávamos aconteceu, e o parâmetro reject
é utilizado para mostrar que algo deu errado.
A estrutura básica de uma Promise é:
new Promise((resolve, reject) => {
if(tudo deu certo)
resolve();
if(ocorreu um erro){
reject();
}
});
Consumindo Promises
Para consumir nossa Promise
, vou fazer o seguinte:
let vaiAprenderPromises = ... //aqui vai o código visto
//chamar nossa promise
let lerConteudo = () => {
vaiAprenderPromises
.then(realizada => { console.log(realizada) })
.catch(rejeitada => { console.log(rejeitada) })
}
lerConteudo();
Quando consumimdo uma promise, nós utilizamos then
para pegar o resolve
, e catch
para pegar o reject
.
Você pode juntar os dois códigos e rodar no seu browser.
Se você fizer, o output vai ser um erro:
Error: sim, eu menti
E não! Nãou sou mentirosa. Eu precisava que isso fosse a saída porque eu não sei como te entregar o biscoito. Mas eu prometo
que eu vou! Então, vamos ver como.
Encadeando Promises
Como eu disse anteriormente, eu prometo
que vou te entregar o biscoito.
let entregarBiscoito = () => {
return new Promise((resolve, reject) => {
resolve("O biscoito está indo!");
})
}
Note que agora nos estamos apenas resolvendo a promise
. Nesses casos, o código pode ser reescrito como abaixo:
let entregarBiscoito = () => {
return Promise.resolve("O biscoito está indo!");
}
A promessa de entregarBiscoito
só pode começar depois de vaiAprenderPromises
.
let mentirosa = false;
let vaiAprenderPromises = ... //aqui vai o código visto antes
let entregarBiscoito = ... //aqui vai o código visto antes
let lerConteudo = () => {
vaiAprenderPromises
.then(entregarBiscoito)
.then(realizada => { console.log(realizada) })
.catch(rejeitada => { console.log(rejeitada) })
}
lerConteudo();
Agora, você pode rodar esse código e preenchê-lo com os code snippets vistos anteriormente.
O output vai ser:
O biscoito está indo!
E aqui está: :cookie:
Async e await
Considere o código a seguir:
fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log(err))
Aqui, estamos usando encadeamento de Promises. Mas imagine um cenário onde dentro de then
você tenha que esperar por outra promise, o que vai gerar outro then
e assim vai. Seriam vários encadeamentos.
Para evitar isso, podemos usar as keywords async
e await
dentro de uma função para encadear promises de um jeito melhor.
const getTodos = async () => {
let response = await fetch('https://jsonplaceholder.typicode.com/todos');
let data = await response.json();
return data;
}
getTodos().then(data => console.log(data));
Primeiro, colocamos todo o código que queremos dentro de uma função, que vai ser async
. Nesse caso, a função vai ser getTodos
. Lembre-se que essa função retorna uma promise, então quando getTodos
é chamada, nós ainda vamos precisar do método then
.
Dentro de uma função async
, nós podemos tratar nossas Promises como um código não bloqueável escrevendo a keyword await
. Não é mais necessário encadear promises e o código fica muito mais legível.
Como aprender mais
Materiais úteis
- https://www.digitalocean.com/community/tutorials/javascript-promises-for-dummies#understanding-promises
- https://www.luiztools.com.br/post/programacao-assincrona-em-nodejs-callbacks-e-promises/
- https://javascript.info/async-await
- https://bigcodenerd.org/resolving-promises-sequentially-javascript/
- https://www.youtube.com/watch?v=CWjNefiE47Y&list=PL4cUxeGkcC9jx2TTZk3IGWKSbtugYdrlu&index=10
- https://www.youtube.com/watch?v=vn3tm0quoqE