JavaScript Assíncrono

Algumas coisas acontecem instantaneamente: somar dois números, ler uma variável, mudar texto na página. Outras levam tempo. Um temporizador conta regressivamente por três segundos. Uma solicitação para um servidor viaja pela rede e volta um momento depois. Se o JavaScript parasse e esperasse por cada uma delas, a página inteira congelaria: sem cliques, sem rolagem, nada, até que a coisa lenta terminasse. Não funciona assim. Este capítulo é sobre como o JavaScript inicia algo lento, mantém a página responsiva e volta para lidar com o resultado quando ele está pronto.
Por que assíncrono
A maioria do JavaScript é executada de cima para baixo, uma linha terminando antes da próxima iniciar. Isso é bom para trabalho rápido. Mas algumas tarefas levam tempo real, e o JavaScript não fica esperando por elas. Ele inicia a tarefa lenta, passa para a próxima linha e volta para a lenta quando termina. Isso é o que assíncrono significa: não acontecendo em ordem, não agora, mas depois, quando o resultado está pronto.
O exemplo mais simples é um temporizador. setTimeout executa um pedaço de código após um atraso:
console.log("Início");
setTimeout(() => {
console.log("3 segundos depois");
}, 3000);
console.log("Fim");
// Início
// Fim
// 3 segundos depoisVeja a ordem. "Fim" é exibido antes de "3 segundos depois", mesmo que venha depois de setTimeout no código. O JavaScript não esperou três segundos nessa linha. Ele configurou o temporizador, continuou, e executou o código atrasado quando o tempo acabou.
"Fim" é exibido antes da mensagem de setTimeout, mesmo que o temporizador venha primeiro no código. Promises
setTimeout é bom para um atraso, mas a maioria do trabalho assíncrono produz um valor que você se importa: os dados que você pediu, ou um erro se algo deu errado. Para isso, JavaScript usa uma promise. Uma promise é um valor que ainda não está pronto, com um espaço reservado para ele. É como um recibo: você não tem a refeição, mas tem algo que se transformará na refeição.
Uma promise termina de uma de duas maneiras. Ela resolve com um valor quando o trabalho é bem-sucedido, ou rejeita com um erro quando falha. Você diz o que fazer em cada caso com .then() e .catch():
loadUser()
.then((user) => {
console.log(`Carregado ${user.name}`);
})
.catch((error) => {
console.log("Algo deu errado");
});.then() é executado quando a promise resolve, e recebe o valor. .catch() é executado se rejeita, e recebe o erro. O código dentro deles é executado depois, uma vez que o resultado chega.
.then() para o valor e .catch() para o erro, e ambos são executados depois uma vez que o resultado está lá. async / await
Cadeias de promises funcionam, mas há uma forma mais limpa de escrevê-las que lê quase como código comum de cima para baixo. Usa duas palavras-chave, async e await.
Você marca uma função async, e dentro dela você pode await uma promise. await pausa a função até que a promise resolve, depois lhe dá o valor diretamente, sem .then() necessário:
async function showUser() {
const user = await loadUser();
console.log(`Carregado ${user.name}`);
}Leia aquilo de cima para baixo: obtenha o usuário, depois registre o nome. A linha await espera que loadUser() termine e lhe entrega o user. A função pausa ali, mas o resto da página continua rodando, então nada congela.
Para tratar erros, envolva o await em try e catch:
async function showUser() {
try {
const user = await loadUser();
console.log(`Carregado ${user.name}`);
} catch (error) {
console.log("Não foi possível carregar o usuário");
}
}Se a promise falhar, o código salta para o bloco catch em vez de travar.
async e você pode await uma promise dentro dela. await pausa aquela função até que a promise resolve e lhe entrega o valor direto, então o código lê de cima para baixo sem .then(). Envolva em try e catch para tratar falhas, e a página continua rodando enquanto a função espera. Busca de dados
A tarefa assíncrona mais comum que você escreverá é carregar dados de um servidor. O navegador lhe dá fetch para isso. Você passa uma URL para ela, e ela retorna uma promise para a resposta:
async function loadUsers() {
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);
}Há dois awaits, e isso surpreende as pessoas no início. O primeiro espera que o servidor responda. O segundo lê o corpo daquela resposta e o transforma em dados JavaScript com .json(), que é em si assíncrono. Então buscar é dois passos: obter a resposta, depois lê-la.
Servidores também podem responder com um erro, como "não encontrado". fetch não trata aquilo como uma falha por si só, então você verifica response.ok você mesmo:
async function loadUsers() {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
console.log("Solicitação falhou");
return;
}
const users = await response.json();
console.log(users);
}fetch(url) retorna uma promise para a resposta do servidor, e carregar dados é dois passos: await fetch(...) para a resposta, depois await response.json() para ler os dados de dentro dela. Um erro de servidor como "não encontrado" não falha por si só, então verifique response.ok antes de ler o corpo. Ambos os passos usam await porque ambos levam tempo. O event loop e executando trabalho em paralelo
Tudo até agora tem um tema: JavaScript inicia trabalho lento, continua, e volta para isso depois. Esta seção é o mecanismo por trás disso, mais os padrões para executar várias tarefas assíncrono bem.
Aqui está uma figura de como o JavaScript mantém o controle de trabalho assíncrono. Seu código em execução fica na call stack, a lista do que está sendo executado agora. Quando você chama setTimeout, o temporizador é entregue ao navegador para contar regressivamente, então não está na stack bloqueando nada. Quando o temporizador termina, seu callback vai para uma fila de tarefas, uma fila de espera de código pronto para ser executado.
O event loop é a parte que os conecta. Tem uma regra: execute tudo na call stack primeiro, e só quando a stack estiver vazia puxe a próxima tarefa da fila. É por isso que setTimeout(fn, 0) ainda é executado após seu outro código, mesmo com um atraso zero. O callback tem que esperar na fila até que o código atual termine:
console.log("primeiro");
setTimeout(() => console.log("terceiro"), 0);
console.log("segundo");
// primeiro
// segundo
// terceiroO atraso de 0 não significa "agora". Significa "assim que o código atual terminar".
setTimeout(fn, 0) ainda é executado após o resto do seu código. Um atraso de 0 significa "assim que o código atual terminar", não "agora". Para onde o assíncrono vai daqui
Assíncrono é a parte do JavaScript que mantém uma página viva enquanto trabalho lento acontece no fundo. A forma é sempre a mesma: inicie algo que leva tempo, deixe o resto do seu código ser executado, e trate o resultado quando chega. Promises dão aquele resultado um valor que você pode passar por aí, async/await permite que você o escreva em código simples de cima para baixo, e fetch é onde você o usará mais, carregando dados de um servidor.
Daqui em diante, assíncrono se conecta às partes da linguagem que reagem ao mundo exterior. Você ligará dados buscados à página através do DOM, e você iniciará trabalho assíncrono em resposta ao que as pessoas fazem na página, que é events. Os resultados que você consegue de volta são quase sempre objetos, então ler e remodelar eles é a próxima habilidade que se emparelha com tudo aqui.

