Descomplicando Promises e Async/Await: O Guia para Tratar Erros em JavaScript
No desenvolvimento JavaScript moderno, lidar com operações assíncronas de forma eficiente é essencial.
Seja chamando APIs, lendo arquivos ou aguardando um timeout, entender Promises, async/await e como gerenciar erros adequadamente pode ajudar você a escrever código mais legível, manutenível e resistente a bugs.
Este artigo percorre os fundamentos e exemplos práticos de Promises em JavaScript, async/await e estratégias para tratamento de erros.
O que são Promises em JavaScript?
Uma Promise em JavaScript representa a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante.
Sintaxe Básica de uma Promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { name: 'Vinícius' };
resolve(data);
}, 1000);
});
};
Uma Promise pode estar em um de três estados:
- Pending – A operação está em andamento.
- Fulfilled – A operação foi concluída com sucesso.
- Rejected – A operação falhou.
Consumindo Promises
fetchData()
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
Aqui, .then() trata a resolução bem-sucedida, e .catch() trata os erros. Mas aninhar muitos desses pode rapidamente levar a uma pirâmide no estilo callback, também conhecida como "Promise hell."
Introduzindo async e await
ES2017 introduziu a sintaxe async e await para lidar com Promises de uma forma mais limpa e legível.
Como Funciona
const getUser = async () => {
try {
const response = await fetchData();
console.log(response);
} catch (error) {
console.error('Error fetching user:', error);
}
};
asyncdeclara que a função sempre retorna uma Promise.awaitpausa a execução até que a Promise seja resolvida ou rejeitada.
Isso torna o código mais limpo e fácil de debugar.
"Código assíncrono não precisa ser difícil de escrever ou entender. async/await é prova disso." – Kyle Simpson, autor de You Don't Know JS
Exemplo Real: Buscando Dados de API
Vamos usar uma API real para demonstrar async/await e tratamento de erros:
const getUserData = async (userId) => {
try {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const user = await res.json();
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
};
Chamando a função:
getUserData(1)
.then(user => console.log(user))
.catch(err => console.log('Handled in calling function:', err));
Melhores Práticas para Tratamento de Erros
1. Sempre Use try/catch com await
Sem isso, Promises rejeitadas irão quebrar sua função async silenciosamente ou propagar erros não capturados.
2. Use .catch() Quando Você Deve Permanecer na Sintaxe de Encadeamento
doSomething()
.then(result => doNext(result))
.catch(error => console.error('Something went wrong:', error));
3. Envolva Chamadas Assíncronas em Funções Utilitárias
Este padrão evita repetir blocos try/catch:
const safe = async (promise) => {
try {
const data = await promise;
return [null, data];
} catch (err) {
return [err, null];
}
};
const [err, data] = await safe(fetchData());
if (err) return console.error('Error:', err);
Armadilhas para Evitar
Falhas Silenciosas
Esquecer de return uma Promise ou não usar await pode levar a comportamentos inesperados.
// Errado
const result = getUserData(1); // retorna Promise, não dados reais
// Correto
const result = await getUserData(1);
Ignorando Rejeições
Rejeições não tratadas podem quebrar sua aplicação no Node.js (a partir da v15+ por padrão).
Misturando .then() e await
Funciona, mas é confuso e inconsistente. Mantenha um estilo por função.
Quando Usar Promises vs async/await
| Caso de Uso | Abordagem Preferida |
|---|---|
| Operações únicas | async/await |
| Encadeando múltiplas operações | async/await |
| Cadeias construídas dinamicamente | .then() |
| Fluxos reativos ou stream-like | Promises / RxJS |
Bônus: Lidando com Múltiplas Promises
Usando Promise.all()
const fetchPostsAndUsers = async () => {
try {
const [posts, users] = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/posts'),
fetch('https://jsonplaceholder.typicode.com/users')
]);
const postsData = await posts.json();
const usersData = await users.json();
console.log(postsData, usersData);
} catch (error) {
console.error('Error fetching posts or users:', error);
}
};
Isso é ótimo quando você quer executar múltiplas tarefas assíncronas em paralelo e aguardar que todas terminem.
Conclusão
Entender como Promises, async/await e tratamento de erros funcionam em JavaScript é fundamental para construir aplicações rápidas, responsivas e estáveis. Essas ferramentas são essenciais ao interagir com APIs, lidar com I/O ou construir recursos em tempo real.
Para recapitular:
- Use
async/awaitpara código mais limpo e com aparência síncrona. - Sempre trate erros com
try/catchou.catch(). - Envolva lógica assíncrona em padrões reutilizáveis quando possível.
Nas palavras de Douglas Crockford, uma das principais vozes do JavaScript:
"JavaScript é a linguagem de programação mais mal interpretada do mundo." — e é seu trabalho entendê-la melhor a cada dia.
Referências
- FLANAGAN, David. JavaScript: The Definitive Guide. 7th ed. O'Reilly Media, 2020.
- SIMPSON, Kyle. You Don't Know JS Yet. 2nd ed. GitHub Open Source Series, 2020.
- Mozilla Developer Network. MDN Web Docs: Promise
- W3Schools. JavaScript async/await