O tratamento de erros é um aspecto fundamental da programação que garante que as aplicações possam lidar de maneira elegante com situações inesperadas. Em JavaScript, embora o try-catch
seja comumente usado, existem técnicas mais avançadas para aprimorar o tratamento de erros.
Este artigo explora esses métodos avançados, fornecendo soluções práticas para melhorar suas estratégias de gerenciamento de erros e tornar suas aplicações mais resilientes.
O que é tratamento de erros?
O propósito do tratamento de erros
O tratamento de erros antecipa, detecta e responde a problemas que surgem durante a execução do programa. Um tratamento de erros adequado melhora a experiência do usuário, mantém a estabilidade da aplicação e garante confiabilidade.
Tipos de erros em JavaScript
- Erros de sintaxe. Estes são erros na sintaxe do código, como parênteses ausentes ou uso incorreto de palavras-chave.
- Erros em tempo de execução. Ocorrem durante a execução, como acessar propriedades de objetos indefinidos.
- Erros lógicos. Esses erros não fazem o programa falhar, mas levam a resultados incorretos, muitas vezes devido a lógica defeituosa ou efeitos colaterais não intencionais.
Por que o try-catch não é suficiente
As limitações do try-catch
- Limitações de escopo. Apenas lida com código síncrono dentro de seu bloco e não afeta operações assíncronas, a menos que sejam tratadas especificamente.
- Falhas silenciosas. O uso excessivo ou inadequado pode levar a erros sendo silenciosamente ignorados, potencialmente causando comportamentos inesperados.
- Propagação de erros. Não suporta nativamente a propagação de erros por diferentes camadas da aplicação.
Quando usar try-catch
- Código síncrono. Efetivo para lidar com erros em operações síncronas como análise de JSON.
- Seções críticas. Use para proteger seções críticas de código onde erros podem ter consequências graves.
Classes de Erro Personalizadas: Aprimorando Informações de Erro
Criando uma Classe de Erro Personalizada
Classes de erro personalizadas estendem a classe integrada Error
para fornecer informações adicionais:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.stack = (new Error()).stack; // Capture the stack trace
}
}
Benefícios de Erros Personalizados
- Clareza. Oferece mensagens de erro específicas.
- Manuseio granular. Permite lidar com tipos específicos de erros separadamente.
- Metadados de erro. Inclui contexto adicional sobre o erro.
Casos de Uso para Erros Personalizados
- Falhas de validação. Erros relacionados à validação de entrada do usuário.
- Erros específicos do domínio. Erros adaptados a domínios específicos de aplicação como autenticação ou processamento de pagamento.
Manuseio Centralizado de Erros
Manuseio Global de Erros no Node.js
Centralize o manuseio de erros usando middleware:
app.use((err, req, res, next) => {
console.error('Global error handler:', err);
res.status(500).json({ message: 'An error occurred' });
});
Manuseio Centralizado de Erros em Aplicações Frontend
Implemente o tratamento centralizado de erros no React usando boundaries de erro:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Vantagens do Tratamento Centralizado de Erros
- Consistência. Garante uma abordagem uniforme para o gerenciamento de erros.
- Manutenção mais fácil. Atualizações centralizadas reduzem o risco de alterações não detectadas.
- Melhor registro e monitoramento. Facilita a integração com ferramentas de monitoramento.
Propagação de Erros
Propagação de Erros em Código Síncrono
Use throw
para propagar erros:
function processData(data) {
try {
validateData(data);
saveData(data);
} catch (error) {
console.error('Failed to process data:', error);
throw error;
}
}
Propagação de Erros em Código Assíncrono
Manuseie erros com promessas ou async
/await
:
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
Quando Propagar Erros
- Erros críticos. Propague erros que afetam toda a aplicação.
- Lógica de negócios. Permita que componentes de nível mais alto lidem com erros de lógica de negócios.
Manuseio de Erros em Código Assíncrono
Tratamento de Erros com async/await
Use try-catch
para gerenciar erros em funções assíncronas:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return await response.json();
} catch (error) {
console.error('Error fetching user data:', error);
return null;
}
}
Usando Promise.all Com Tratamento de Erros
Manuseie múltiplas promessas e erros:
async function fetchMultipleData(urls) {
try {
const responses = await Promise.all(urls.map
(url => fetch(url)));
return await Promise.all(responses.map(response => {
if (!response.ok) {
throw new Error(`Failed to fetch ${response.url}`);
}
return response.json();
}));
} catch (error) {
console.error('Error fetching multiple data:', error);
return [];
}
}
Problemas Comuns no Tratamento de Erros Assíncronos
- Promessas não capturadas. Sempre trate promessas usando
await
,.then()
ou.catch()
. - Falhas silenciosas. Garanta que os erros não sejam engolidos silenciosamente.
- Condições de corrida. Tenha cuidado com operações assíncronas concorrentes.
Registro de Erros
Registro de Erros do Lado do Cliente
Capture erros globais:
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error captured:', message, source, lineno, colno, error);
sendErrorToService({ message, source, lineno, colno, error });
};
Registro de Erros do Lado do Servidor
Use ferramentas como Winston para registro do lado do servidor:
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'error.log' })]
});
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('An error occurred');
});
Monitoramento e Alertas
Configure monitoramento em tempo real e alertas com serviços como PagerDuty ou Slack:
function notifyError(error) {
// Send error details to monitoring service
}
Melhores Práticas para Registro de Erros
- Inclua contexto. Registre informações adicionais como dados de solicitação e informações do usuário.
- Evite sobrecarga de registros. Registre informações essenciais para evitar problemas de desempenho.
- Analise os registros regularmente. Revise os registros regularmente para detectar e resolver problemas recorrentes.
Degradação Graciosa e Alternativas
Degradação Graciosa
Projete seu aplicativo para continuar funcionando com capacidades reduzidas:
function renderProfilePicture(user) {
try {
if (!user.profilePicture) {
throw new Error('Profile picture not available');
}
return `<img data-fr-src="${user.profilePicture}" alt="Profile Picture">`;
} catch (error) {
console.error('Error rendering profile picture:', error.message);
return '<img src="/default-profile.png" alt="Default Profile Picture">';
}
}
Mecanismos de Retorno
Forneça alternativas quando as operações primárias falharem:
async function fetchDataWithFallback(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
return { message: 'Default data' }; // Fallback data
}
}
Implementando Degradação Graciosa
- Alternativas de UI. Forneça elementos de UI alternativos quando recursos falharem.
- Alternativas de Dados. Use valores em cache ou padrões quando dados ao vivo não estiverem disponíveis.
- Mecanismos de Retentativa. Implemente lógica de retentativa para erros transientes.
Equilibrando a Degradação Graciosa
Equilibrar a disponibilização de fallbacks com a informação aos usuários sobre problemas:
function showErrorNotification(message) {
// Notify users about the issue
}
Testando o Tratamento de Erros
Teste Unitário do Tratamento de Erros
Verificar o tratamento de erros em funções individuais:
const { validateUserInput } = require('./validation');
test('throws error for invalid username', () => {
expect(() => {
validateUserInput({ username: 'ab' });
}).toThrow('Username must be at least 3 characters long.');
});
Teste de Integração
Teste o tratamento de erros em diferentes camadas de aplicação:
test('fetches data with fallback on error', async () => {
fetch.mockReject(new Error('Network error'));
const data = await fetchDataWithFallback('https://api.example.com/data');
expect(data).toEqual({ message: 'Default data' });
});
Teste de Ponta a Ponta
Simular cenários do mundo real para testar o tratamento de erros:
describe('ErrorBoundary', () => {
it('displays error message on error', () => {
cy.mount(<ErrorBoundary><MyComponent /></ErrorBoundary>);
cy.get(MyComponent).then(component => {
component.simulateError(new Error('Test error'));
});
cy.contains('Something went wrong.').should('be.visible');
});
});
Melhores Práticas para Testar o Tratamento de Erros
- Cubra casos extremos. Garanta que os testes abordem vários cenários de erro.
- Teste os fallbacks. Verifique se os mecanismos de fallback funcionam conforme o esperado.
- Automatize os testes. Utilize pipelines de CI/CD para automatizar e garantir um tratamento de erros robusto.
Cenários do Mundo Real
Cenário 1: Sistema de Processamento de Pagamentos
Gerenciar erros durante o processamento de pagamentos:
- Classes de erro personalizadas. Utilize classes como
CardValidationError
,PaymentGatewayError
. - Lógica de tentativa. Implemente tentativas para problemas relacionados à rede.
- Registro centralizado. Monitore erros de pagamento e resolva problemas prontamente.
Cenário 2: Aplicações Intensivas em Dados
Gerenciar erros no processamento de dados:
- Degradação graciosa. Forneça dados parciais ou visualizações alternativas.
- Dados de fallback. Utilize valores em cache ou padrão.
- Registro de erros. Registre contexto detalhado para solução de problemas.
Cenário 3: Autenticação e Autorização do Usuário
Tratar erros de autenticação e autorização:
- Classes de erro personalizadas. Crie classes como
AuthenticationError
,AuthorizationError
. - Manuseio centralizado. Registre e monitore problemas relacionados à autenticação.
- Degradação graciosa. Ofereça opções de login alternativas e mensagens de erro significativas.
Conclusão
O tratamento avançado de erros em JavaScript requer ir além do simples try-catch
para adotar erros personalizados, manuseio centralizado, propagação e testes robustos. Implementar essas técnicas permite construir aplicações resilientes que oferecem uma experiência de usuário contínua, mesmo quando algo dá errado.
Leitura Adicional
- “JavaScript: The Good Parts” por Douglas Crockford
- “Você Não Sabe JS: Async & Performance” por Kyle Simpson
- MDN Web Docs: Tratamento de Erros
Source:
https://dzone.com/articles/advanced-error-handling-in-javascript-custom-error