async/await
Esta aula aborda a sintaxe async/await em JavaScript, uma forma moderna de lidar com operações assíncronas. Você aprenderá como declarar funções assíncronas, usar await para aguardar Promises, tratar erros com try/catch e executar tarefas em sequência ou paralelo.
async/await é uma sintaxe introduzida no ECMAScript 2017 (ES8) que simplifica o trabalho com Promises, tornando o código assíncrono mais legível e semelhante a código síncrono. Em vez de encadear .then() e .catch(), você pode escrever funções que 'pausam' a execução até que uma Promise seja resolvida ou rejeitada, facilitando o fluxo de controle.
Nesta aula, exploraremos como usar async/await para escrever código assíncrono de forma clara, como tratar erros adequadamente e como decidir entre execução sequencial e paralela para otimizar o desempenho.
Sintaxe
A palavra-chave async é colocada antes de uma declaração de função (seja ela tradicional, arrow ou expressão) para indicar que a função sempre retornará uma Promise. Dentro de uma função async, você pode usar await antes de uma expressão que retorna uma Promise, fazendo com que a execução da função espere até que a Promise seja resolvida (ou rejeitada).
Exemplo: converter uma função que retorna uma Promise para usar async/await.
// Função que retorna uma Promise (versão tradicional)
function buscarDados() {
return new Promise((resolve) => {
setTimeout(() => resolve("Dados carregados"), 1000);
});
}
// Função assíncrona usando async/await
async function obterDados() {
const resultado = await buscarDados();
console.log(resultado); // "Dados carregados"
return resultado;
}
obterDados();Observe que obterDados é declarada com async e dentro dela usamos await para aguardar a Promise retornada por buscarDados. O valor resolvido é atribuído à variável resultado. A função obterDados também retorna uma Promise (implicitamente), que será resolvida com o valor de resultado.
É importante lembrar que await só pode ser usado dentro de funções async. Fora delas, você ainda precisa usar .then() ou encapsular em uma função assíncrona.
Tratamento de erro com try/catch
Uma das grandes vantagens do async/await é a capacidade de usar blocos try/catch para capturar erros de Promises rejeitadas, de forma similar ao tratamento de exceções síncronas. Se uma Promise rejeitada for aguardada com await, o erro será lançado como uma exceção, que pode ser capturada com catch.
Exemplo: simular uma requisição que pode falhar.
async function buscarUsuario(id) {
if (id < 0) {
throw new Error("ID inválido");
}
// Simula uma chamada de API
return { id, nome: "João" };
}
async function exibirUsuario(id) {
try {
const usuario = await buscarUsuario(id);
console.log("Usuário:", usuario);
} catch (erro) {
console.error("Erro ao buscar usuário:", erro.message);
}
}
exibirUsuario(-1); // "Erro ao buscar usuário: ID inválido"
exibirUsuario(1); // "Usuário: { id: 1, nome: 'João' }"No exemplo, se buscarUsuario lançar uma exceção (ou retornar uma Promise rejeitada), o catch a capturará. Você também pode usar finally para executar código independentemente de sucesso ou falha.
É uma boa prática sempre tratar erros em funções assíncronas para evitar Promises rejeitadas não tratadas, que podem causar comportamentos inesperados ou vazamentos de memória.
Sequencial vs paralelo
Ao lidar com múltiplas operações assíncronas, você precisa decidir se elas devem ser executadas em sequência (uma após a outra) ou em paralelo (simultaneamente). O async/await facilita ambas as abordagens.
Execução sequencial: Use await para cada operação, uma após a outra. Isso é útil quando cada operação depende do resultado da anterior.
async function sequencial() {
const a = await operacao1(); // 2 segundos
const b = await operacao2(); // 2 segundos
const c = await operacao3(); // 2 segundos
// total: ~6 segundos
return [a, b, c];
}Execução paralela: Para iniciar todas as operações ao mesmo tempo, use Promise.all (ou Promise.allSettled) com um array de Promises. Você pode iniciar as Promises sem await e depois aguardar todas com await Promise.all().
async function paralelo() {
const promise1 = operacao1(); // inicia imediatamente
const promise2 = operacao2(); // inicia imediatamente
const promise3 = operacao3(); // inicia imediatamente
const resultados = await Promise.all([promise1, promise2, promise3]);
// total: ~2 segundos (a mais lenta)
return resultados;
}Outra abordagem paralela é usar await em um array de Promises com Promise.all diretamente: const resultados = await Promise.all([operacao1(), operacao2(), operacao3()]);.
Escolha entre sequencial e paralelo com base na dependência entre as operações. Se não houver dependência, prefira paralelo para melhor performance.
Boas práticas e observações finais
- Sempre trate erros com
try/catchem funçõesasync. - Não misture
.then()comawaitdesnecessariamente; escolha um estilo e mantenha consistência. - Evite usar
awaitem loopsforEach; prefirafor...ofouPromise.allcommap. - Use
Promise.allpara paralelismo ePromise.allSettledquando precisar de resultados parciais. - Lembre-se de que funções
asyncsempre retornam uma Promise, então você pode encadear.then()se necessário.
Referências
- MDN: async function
- MDN: await
- MDN: Promise.all()
- MDN: Promise.allSettled()
- JavaScript.info: Async/await
- Node.js: Async/await
Exercícios
Crie uma função assíncrona chamada
esperarESaudacaoque recebe um nome e um tempo em milissegundos. A função deve aguardar o tempo especificado (usandosetTimeoutcom Promise) e então exibir no console: "Olá, [nome]!"✓ Resposta:function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function esperarESaudacao(nome, tempo) { await delay(tempo); console.log(`Olá, ${nome}!`); } // Teste: esperarESaudacao("Maria", 1000); // após 1s: Olá, Maria!Escreva uma função assíncrona
buscarDadosComErroque simula uma requisição que rejeita com a mensagem "Erro de rede". Usetry/catchpara capturar o erro e exibir no console "Erro capturado: [mensagem]".✓ Resposta:async function buscarDadosComErro() { try { const dados = await new Promise((_, reject) => setTimeout(() => reject(new Error("Erro de rede")), 500) ); console.log(dados); } catch (erro) { console.log("Erro capturado:", erro.message); } } buscarDadosComErro(); // "Erro capturado: Erro de rede"Implemente uma função
sequencialque aguarda três Promises que resolvem após 1, 2 e 3 segundos respectivamente, retornando um array com os valores resolvidos. Useawaitsequencial.✓ Resposta:function criarPromise(valor, tempo) { return new Promise(resolve => setTimeout(() => resolve(valor), tempo)); } async function sequencial() { const a = await criarPromise("A", 1000); const b = await criarPromise("B", 2000); const c = await criarPromise("C", 3000); return [a, b, c]; } sequencial().then(console.log); // Após ~6s: ["A", "B", "C"]Reescreva a função do exercício 3 para executar as Promises em paralelo usando
Promise.all. Qual o tempo total aproximado?✓ Resposta:async function paralelo() { const promises = [ criarPromise("A", 1000), criarPromise("B", 2000), criarPromise("C", 3000) ]; return await Promise.all(promises); } paralelo().then(console.log); // Após ~3s: ["A", "B", "C"] // O tempo total é o da Promise mais lenta (3 segundos).Crie uma função assíncrona
obterUsuariosque faz duas chamadas paralelas: uma para buscar uma lista de usuários (demora 2s) e outra para buscar permissões (demora 1s). UsePromise.alle retorne um objeto com ambas as listas. Simule as chamadas com Promises.✓ Resposta:function buscarUsuarios() { return new Promise(resolve => setTimeout(() => resolve(["João", "Maria"]), 2000) ); } function buscarPermissoes() { return new Promise(resolve => setTimeout(() => resolve(["admin", "user"]), 1000) ); } async function obterUsuarios() { const [usuarios, permissoes] = await Promise.all([ buscarUsuarios(), buscarPermissoes() ]); return { usuarios, permissoes }; } obterUsuarios().then(console.log); // Após ~2s: { usuarios: ["João", "Maria"], permissoes: ["admin", "user"] }