Dominando Promises e Async/Await em JavaScript
Entenda de vez programação assíncrona: callbacks, Promises, async/await, tratamento de erros e padrões avançados com exemplos práticos.
Por que JavaScript é assíncrono
JavaScript é single-threaded (uma thread de execução). Para evitar bloquear a UI ou o servidor durante operações demoradas (requisições HTTP, leitura de arquivos, timers), JavaScript usa programação assíncrona.
Operações assíncronas comuns
- Requisições HTTP (fetch, axios)
- Leitura/escrita de arquivos (Node.js)
- Timers (setTimeout, setInterval)
- Acesso a bancos de dados
- WebSockets e eventos
Evolução: Callbacks → Promises → Async/Await
1. Callbacks (o jeito antigo)
Funções passadas como argumento para serem executadas quando a operação termina.
// Exemplo básico
setTimeout(() => {
console.log('Executou após 1 segundo');
}, 1000);
// Problema: Callback Hell (pirâmide da morte)
getUser(userId, (error, user) => {
if (error) {
console.error(error);
return;
}
getPosts(user.id, (error, posts) => {
if (error) {
console.error(error);
return;
}
getComments(posts[0].id, (error, comments) => {
if (error) {
console.error(error);
return;
}
console.log(comments);
});
});
});
2. Promises (ES6/2015)
Promises representam um valor que estará disponível no futuro e têm 3 estados:
- Pending: operação ainda não terminou
- Fulfilled: operação completou com sucesso (resolve)
- Rejected: operação falhou (reject)
Criando uma Promise
const minhaPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const sucesso = true;
if (sucesso) {
resolve('Operação completada!');
} else {
reject('Erro na operação');
}
}, 1000);
});
// Consumindo a Promise
minhaPromise
.then(resultado => console.log(resultado))
.catch(erro => console.error(erro));
Encadeamento (Promise chaining)
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
3. Async/Await (ES2017)
Syntactic sugar sobre Promises. Deixa código assíncrono parecer síncrono.
async function buscarDados() {
try {
const user = await getUser(userId);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
console.log(comments);
} catch (error) {
console.error(error);
}
}
buscarDados();
Promises na prática
Exemplo 1: Requisição HTTP com fetch
// Com Promises
fetch('https://api.example.com/users')
.then(response => {
if (!response.ok) {
throw new Error('Erro na requisição');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error(error));
Exemplo 2: Simulando operação assíncrona
function buscarUsuario(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const usuarios = {
1: { id: 1, nome: 'João' },
2: { id: 2, nome: 'Maria' }
};
const usuario = usuarios[id];
if (usuario) {
resolve(usuario);
} else {
reject(new Error('Usuário não encontrado'));
}
}, 1000);
});
}
// Usar
buscarUsuario(1)
.then(user => console.log(user))
.catch(err => console.error(err));
Exemplo 3: Promise.all (executar em paralelo)
const promise1 = fetch('https://api.example.com/users');
const promise2 = fetch('https://api.example.com/posts');
const promise3 = fetch('https://api.example.com/comments');
Promise.all([promise1, promise2, promise3])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
console.log('Usuários:', users);
console.log('Posts:', posts);
console.log('Comentários:', comments);
})
.catch(error => console.error('Alguma requisição falhou:', error));
Async/Await na prática
Exemplo 1: Requisição HTTP com async/await
async function buscarUsuarios() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Erro ao buscar usuários:', error);
throw error;
}
}
buscarUsuarios();
Exemplo 2: Múltiplas operações sequenciais
async function processarPedido(pedidoId) {
try {
console.log('Buscando pedido...');
const pedido = await buscarPedido(pedidoId);
console.log('Validando estoque...');
const estoqueOk = await validarEstoque(pedido.items);
if (!estoqueOk) {
throw new Error('Estoque insuficiente');
}
console.log('Processando pagamento...');
const pagamento = await processarPagamento(pedido.valor);
console.log('Enviando email...');
await enviarEmailConfirmacao(pedido.email);
console.log('Pedido processado com sucesso!');
return { pedido, pagamento };
} catch (error) {
console.error('Erro ao processar pedido:', error);
throw error;
}
}
processarPedido(123);
Exemplo 3: Operações em paralelo com Promise.all
async function buscarDadosCompletos(userId) {
try {
// Executa as 3 requisições simultaneamente
const [user, posts, friends] = await Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/users/${userId}/posts`).then(r => r.json()),
fetch(`/api/users/${userId}/friends`).then(r => r.json())
]);
return { user, posts, friends };
} catch (error) {
console.error('Erro ao buscar dados:', error);
throw error;
}
}
buscarDadosCompletos(1)
.then(dados => console.log(dados));
Métodos úteis de Promise
Promise.all (todas devem completar)
// Espera todas completarem. Se uma falhar, rejeita tudo.
const results = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
// results = [users, posts, comments]
Promise.allSettled (espera todas, mesmo com erro)
// Espera todas completarem, mesmo com erros
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} sucesso:`, result.value);
} else {
console.error(`Promise ${index} falhou:`, result.reason);
}
});
Promise.race (primeira a completar vence)
// Retorna a primeira Promise a completar (sucesso ou erro)
const fastest = await Promise.race([
fetch('https://api1.example.com/data'),
fetch('https://api2.example.com/data'),
fetch('https://api3.example.com/data')
]);
console.log('API mais rápida respondeu:', fastest);
Promise.any (primeira a dar sucesso)
// Retorna a primeira Promise a dar sucesso (ignora erros)
const result = await Promise.any([
fetch('https://api1.example.com/data'),
fetch('https://api2.example.com/data'),
fetch('https://api3.example.com/data')
]);
console.log('Primeira a dar sucesso:', result);
Tratamento de erros
Com Promises (.catch)
fetch('/api/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
console.error('Erro capturado:', error);
// Tratar erro
})
.finally(() => {
console.log('Sempre executa');
});
Com async/await (try/catch)
async function buscarDados() {
try {
const response = await fetch('/api/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Erro capturado:', error);
// Tratar erro
} finally {
console.log('Sempre executa');
}
}
Tratamento granular de erros
async function salvarUsuario(userData) {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (response.status === 401) {
throw new Error('Não autorizado');
}
if (response.status === 400) {
const errorData = await response.json();
throw new Error(`Validação falhou: ${errorData.message}`);
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.message.includes('Não autorizado')) {
// Redirecionar para login
console.error('Redirecionar para login');
} else if (error.message.includes('Validação')) {
// Mostrar erros de validação
console.error('Erros de validação:', error.message);
} else {
// Erro genérico
console.error('Erro ao salvar usuário:', error);
}
throw error;
}
}
Padrões avançados
1. Retry automático
async function fetchComRetry(url, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('HTTP error');
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`Tentativa ${i + 1} falhou. Tentando novamente...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
fetchComRetry('https://api.example.com/data', 3, 2000);
2. Timeout para Promises
function promiseComTimeout(promise, timeout = 5000) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
}
// Usar
try {
const data = await promiseComTimeout(
fetch('https://api.example.com/slow-endpoint'),
3000
);
console.log(data);
} catch (error) {
console.error('Timeout ou erro:', error);
}
3. Debounce assíncrono (para search inputs)
function debounce(func, delay = 300) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
return new Promise(resolve => {
timeoutId = setTimeout(() => resolve(func(...args)), delay);
});
};
}
const buscarAPI = debounce(async (query) => {
const response = await fetch(`/api/search?q=${query}`);
return await response.json();
}, 500);
// Usar em input de busca
input.addEventListener('input', async (e) => {
const results = await buscarAPI(e.target.value);
mostrarResultados(results);
});
4. Executar Promises em lote (batching)
async function processarEmLotes(items, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(item => processarItem(item))
);
results.push(...batchResults);
console.log(`Processados ${i + batch.length} de ${items.length}`);
}
return results;
}
// Processar 1000 items em lotes de 50
processarEmLotes(items, 50);
5. Cancelando Promises com AbortController
async function fetchComCancelamento() {
const controller = new AbortController();
const signal = controller.signal;
// Cancelar após 5 segundos
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('/api/data', { signal });
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Requisição cancelada');
} else {
throw error;
}
}
}
fetchComCancelamento();
Erros comuns e como evitar
1. Esquecer await
// Errado: retorna Promise, não o valor
async function buscar() {
const data = fetch('/api/users'); // Falta await!
console.log(data); // Promise {<pending>}
}
// Correto
async function buscar() {
const response = await fetch('/api/users');
const data = await response.json();
console.log(data); // Dados reais
}
2. Não tratar erros
// Errado: erro não tratado
async function buscar() {
const data = await fetch('/api/users');
}
// Correto
async function buscar() {
try {
const data = await fetch('/api/users');
} catch (error) {
console.error('Erro:', error);
}
}
3. Await desnecessário em sequência
// Lento: executa em sequência (3 segundos)
async function buscarTudo() {
const users = await fetchUsers(); // 1s
const posts = await fetchPosts(); // 1s
const comments = await fetchComments(); // 1s
}
// Rápido: executa em paralelo (1 segundo)
async function buscarTudo() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
}
Boas práticas
- Sempre trate erros: use try/catch ou .catch()
- Use Promise.all para paralelizar: quando operações são independentes
- Evite await desnecessário: se não usa o resultado imediatamente
- Prefira async/await sobre Promises: código mais legível
- Não misture callbacks com Promises: escolha um padrão
Conclusão
Promises e async/await tornaram JavaScript assíncrono muito mais legível e manutenível. Comece entendendo Promises e, depois, migre para async/await. Use Promise.all para paralelizar, trate erros com try/catch e conheça os métodos úteis (allSettled, race, any). Com esses conceitos, você dominará programação assíncrona em JavaScript.
FAQ
Async/await é mais rápido que Promises?
Não. São equivalentes em performance. Async/await é apenas syntactic sugar sobre Promises.
Posso usar await fora de funções async?
Apenas em top-level await (módulos ES6). Dentro de funções, precisa ser async.
Como cancelar uma Promise?
Use AbortController com fetch ou implemente lógica de cancelamento manual. Promises não têm cancelamento nativo.
O que acontece se eu não usar await?
A função retorna uma Promise pendente, não o valor resolvido. Você precisa usar .then() ou await.
Posso usar async/await no navegador?
Sim, todos os navegadores modernos suportam (Chrome 55+, Firefox 52+, Safari 10.1+). Para navegadores antigos, use Babel.