Tratamento de erros assíncronos no JavaScript
1. Fundamentos do tratamento de erros em operações assíncronas
1.1. Diferenças entre erros síncronos e assíncronos
O try/catch tradicional só captura erros que ocorrem no mesmo "loop de eventos" (event loop). Erros que acontecem dentro de callbacks, Promises ou operações assíncronas escapam desse bloco:
// Isso NÃO captura um erro assíncrono
try {
setTimeout(() => {
throw new Error('Falha assíncrona');
}, 1000);
} catch (err) {
console.error('Capturado:', err); // Isso nunca será executado
}
// O erro acima quebra a aplicação com "Uncaught Error"
1.2. Callbacks e o padrão "error-first"
No Node.js, o padrão clássico para callbacks assíncronos é o "error-first":
const fs = require('fs');
fs.readFile('/arquivo-inexistente.txt', 'utf8', (err, data) => {
if (err) {
console.error('Erro ao ler arquivo:', err.message);
return;
}
console.log('Conteúdo:', data);
});
1.3. O problema de errar silenciosamente
Erros não tratados em callbacks e Promises podem passar despercebidos:
// Promise rejeitada sem tratamento
const promessaPerigosa = new Promise((_, reject) => {
reject(new Error('Rejeitada sem .catch()'));
});
// Isso gera um "UnhandledPromiseRejection" no Node.js
// Silenciosamente falha no navegador (depende do ambiente)
2. Tratamento de erros com Promises
2.1. .catch() em cadeias de Promises
fetch('https://api.exemplo.com/dados')
.then(response => response.json())
.then(data => {
console.log('Dados recebidos:', data);
})
.catch(err => {
console.error('Falha na requisição:', err.message);
});
2.2. Propagação de erros em then()
Erros se propagam automaticamente pela cadeia de Promises:
Promise.resolve(42)
.then(valor => {
throw new Error('Erro no primeiro then');
})
.then(() => {
// Este bloco NUNCA será executado
console.log('Isso não aparece');
})
.catch(err => {
console.error('Capturado no final:', err.message);
// "Capturado no final: Erro no primeiro then"
});
2.3. Operações paralelas e tratamento de erros
const promises = [
fetch('/api/usuario'),
fetch('/api/pedidos'),
fetch('/api/produtos')
];
// Promise.all falha no primeiro erro
Promise.all(promises)
.then(respostas => Promise.all(respostas.map(r => r.json())))
.catch(err => console.error('Uma requisição falhou:', err));
// Promise.allSettled coleta todos os resultados (sucesso ou falha)
Promise.allSettled(promises)
.then(resultados => {
resultados.forEach((r, i) => {
if (r.status === 'rejected') {
console.error(`Promise ${i} rejeitada:`, r.reason);
}
});
});
3. Tratamento de erros com async/await
3.1. try/catch com await
async function buscarDados() {
try {
const response = await fetch('https://api.exemplo.com/dados');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (err) {
console.error('Erro na operação:', err.message);
throw err; // Re-lança para quem chamou tratar
}
}
3.2. Tratamento granular vs. centralizado
// Abordagem granular: trata erros perto da origem
async function salvarUsuario(usuario) {
try {
await db.insert('usuarios', usuario);
} catch (err) {
if (err.code === 'ER_DUP_ENTRY') {
throw new Error('Usuário já existe');
}
throw new Error('Falha ao salvar usuário');
}
}
// Abordagem centralizada: trata erros no nível mais alto
async function handler(req, res) {
try {
const usuario = await salvarUsuario(req.body);
res.json(usuario);
} catch (err) {
res.status(400).json({ erro: err.message });
}
}
3.3. Erros em loops assíncronos
async function processarItens(itens) {
const resultados = [];
for (const item of itens) {
try {
const resultado = await fetch(`/api/processar/${item}`);
resultados.push(await resultado.json());
} catch (err) {
console.error(`Falha ao processar ${item}:`, err.message);
// Continua com o próximo item
}
}
return resultados;
}
4. Erros não capturados e eventos globais
4.1. process.on('unhandledRejection') no Node.js
process.on('unhandledRejection', (reason, promise) => {
console.error('Promise rejeitada sem tratamento:', reason);
// Idealmente, registrar em sistema de log
});
4.2. process.on('uncaughtException')
process.on('uncaughtException', (err) => {
console.error('Erro não capturado:', err);
// Limpar recursos e finalizar graciosamente
process.exit(1);
});
4.3. No navegador/React
// No navegador
window.addEventListener('unhandledrejection', (event) => {
console.error('Promise rejeitada:', event.reason);
event.preventDefault(); // Evita log no console
});
// No React, combine com Error Boundary
5. Tratamento de erros em APIs e requisições HTTP
5.1. Padrões com fetch e axios
// Com fetch
async function buscarComFetch(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erro HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
// Com axios (trata 4xx e 5xx como erro automaticamente)
import axios from 'axios';
async function buscarComAxios(url) {
try {
const { data } = await axios.get(url);
return data;
} catch (err) {
if (err.response) {
console.error('Status:', err.response.status);
console.error('Dados:', err.response.data);
} else if (err.request) {
console.error('Sem resposta do servidor');
} else {
console.error('Erro na configuração:', err.message);
}
throw err;
}
}
5.2. Timeout e cancelamento com AbortController
function buscarComTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
return fetch(url, { signal: controller.signal })
.then(response => {
clearTimeout(timeout);
return response.json();
})
.catch(err => {
clearTimeout(timeout);
if (err.name === 'AbortError') {
throw new Error('Requisição cancelada por timeout');
}
throw err;
});
}
5.3. Retry com backoff
async function fetchComRetry(url, maxRetries = 3) {
for (let tentativa = 1; tentativa <= maxRetries; tentativa++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (err) {
if (tentativa === maxRetries) throw err;
const delay = Math.pow(2, tentativa) * 1000; // Backoff exponencial
console.log(`Tentativa ${tentativa} falhou. Tentando novamente em ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
6. Tratamento de erros assíncronos no ecossistema React
6.1. Error Boundaries e componentes assíncronos
// Error Boundary (classe) não captura erros em event handlers assíncronos
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error('Erro capturado:', error, info);
}
render() {
if (this.state.hasError) {
return <h1>Algo deu errado</h1>;
}
return this.props.children;
}
}
6.2. Tratamento em hooks
function useDados(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
if (!cancelled) setData(result);
} catch (err) {
if (!cancelled) setError(err);
} finally {
if (!cancelled) setLoading(false);
}
}
fetchData();
return () => { cancelled = true; };
}, [url]);
return { data, error, loading };
}
6.3. Gerenciamento com React Query
import { useQuery } from '@tanstack/react-query';
function Usuarios() {
const { data, error, isLoading, refetch } = useQuery({
queryKey: ['usuarios'],
queryFn: async () => {
const res = await fetch('/api/usuarios');
if (!res.ok) throw new Error('Falha ao carregar usuários');
return res.json();
},
retry: 3,
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
});
if (isLoading) return <div>Carregando...</div>;
if (error) return <div>Erro: {error.message}</div>;
return <div>{/* renderizar dados */}</div>;
}
7. Boas práticas e padrões avançados
7.1. Wrappers de erro
function asyncHandler(fn) {
return function(...args) {
return fn(...args).catch(err => {
console.error('Erro na função assíncrona:', err);
// Lógica centralizada de erro
});
};
}
const buscarSeguro = asyncHandler(async (url) => {
const response = await fetch(url);
return response.json();
});
7.2. Erros customizados
class ApiError extends Error {
constructor(status, message) {
super(message);
this.name = 'ApiError';
this.status = status;
this.timestamp = new Date().toISOString();
}
}
// Uso
throw new ApiError(404, 'Recurso não encontrado');
7.3. Logging e monitoramento
// Integração com Sentry
import * as Sentry from '@sentry/react';
async function operacaoCritica() {
try {
return await operacaoArriscada();
} catch (err) {
Sentry.captureException(err);
throw err; // Re-lança para tratamento local
}
}
8. Armadilhas comuns e depuração
8.1. Promises "órfãs"
// PROBLEMA: Promise não retornada
async function processar() {
fetch('/api/dados').then(data => {
console.log(data);
});
// Se fetch rejeitar, o erro fica órfão
}
// SOLUÇÃO: Sempre retornar ou usar await
async function processar() {
try {
const data = await fetch('/api/dados');
console.log(data);
} catch (err) {
console.error(err);
}
}
8.2. Erros em setTimeout e event emitters
// setTimeout não captura rejeições
setTimeout(() => {
Promise.reject(new Error('Erro no timeout'));
}, 1000);
// Isso gera unhandledRejection
// SOLUÇÃO: Tratar dentro do callback
setTimeout(() => {
Promise.reject(new Error('Erro no timeout'))
.catch(err => console.error('Capturado:', err));
}, 1000);
8.3. Stack traces assíncronos
// Node.js com async stack traces (--async-stack-traces)
async function nivel1() {
await nivel2();
}
async function nivel2() {
throw new Error('Erro no nível 2');
}
nivel1().catch(err => {
console.error(err.stack);
// Mostra a cadeia completa de chamadas assíncronas
});
Referências
- MDN Web Docs: Promise — Documentação oficial sobre Promises, incluindo métodos de tratamento de erros como
.catch()ePromise.allSettled() - Node.js Documentation: Error Handling — Guia oficial do Node.js sobre tratamento de erros assíncronos, incluindo
unhandledRejectioneuncaughtException - React Documentation: Error Boundaries — Documentação oficial do React sobre Error Boundaries e suas limitações com código assíncrono
- Axios Documentation: Error Handling — Guia oficial do Axios para tratamento de erros em requisições HTTP, incluindo validação de status codes
- Sentry Documentation: JavaScript Error Handling — Guia completo de monitoramento de erros em JavaScript com Sentry, incluindo contexto assíncrono
- JavaScript.info: Async/await Error Handling — Tutorial detalhado sobre tratamento de erros com async/await, com exemplos práticos e armadilhas comuns