O tratamento de erros é um aspecto fundamental da programação que garante que as aplicações possam lidar de forma elegante com situações inesperadas. Em JavaScript, embora try-catch
seja comumente utilizado, 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 faltando 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 causam a falha do programa, mas levam a resultados incorretos, muitas vezes devido a uma lógica defeituosa ou efeitos colaterais não intencionais.
Por Que try-catch Não É Suficiente
As Limitações do try-catch
- Limitações de escopo. Trata apenas o código síncrono dentro de seu bloco e não afeta operações assíncronas, a menos que seja tratado 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 através de diferentes camadas da aplicação.
Quando usar try-catch
- Código síncrono. Eficiente 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: Aumentando as Informações de Erro
Criando uma Classe de Erro Personalizada
As classes de erro personalizadas estendem a classe embutida 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.
- Manipulação granular. Permite lidar separadamente com tipos específicos de erros.
- Metadados de erro. Inclui contexto adicional sobre o erro.
Cenários de Uso para Erros Personalizados
- Falhas de validação. Erros relacionados à validação de entrada do usuário.
- Erros específicos de domínio. Erros adaptados a domínios de aplicação específicos como autenticação ou processamento de pagamentos.
Manipulação Centralizada de Erros
Manipulação Global de Erros no Node.js
Centralize a manipulação de erros usando middlewares:
app.use((err, req, res, next) => {
console.error('Global error handler:', err);
res.status(500).json({ message: 'An error occurred' });
});
Manipulação Centralizada de Erros em Aplicações Frontend
Implemente o tratamento centralizado de erros no React usando limites 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.
Propagando 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
Trate 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.
- Logica de negócios. Permita que componentes de nível superior lidem com erros de lógica de negócios.
Tratando Erros em Código Assíncrono
Tratamento de Erros com async/await
Use try-catch
para gerenciar erros em funções async:
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
Manipule várias 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 silenciosamente ignorados.
- Condições de corrida. Tenha cautela com operações assíncronas concorrentes.
Registro de erros
Registro de erros do lado do cliente
Captura de 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
Utilize ferramentas como o 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 e alertas em tempo real 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 de usuário.
- Avoid overlogging. Registre informações essenciais para evitar problemas de desempenho.
- Analise os registros regularmente. Revise regularmente os registros para detectar e resolver problemas recorrentes.
Degradação Graciosa e Alternativas
Degradação Graciosa
Projete sua aplicação 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 Alternativa
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 a Degradação Graciosa
- Alternativas de IU. Forneça elementos de IU alternativos quando as funcionalidades falharem.
- Alternativas de Dados. Utilize valores em cache ou padrão quando os dados ao vivo não estiverem disponíveis.
- Mecanismos de Retentativa. Implemente lógica de retentativa para erros transitórios.
Balanceamento da Degradação Graciosa
Equilibrar a prestação de alternativas com manter os usuários informados sobre problemas:
function showErrorNotification(message) {
// Notify users about the issue
}
Testando o Tratamento de Erros
Testando Unitariamente o 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
Testar o tratamento de erros em diferentes camadas da 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
- Cobrir casos extremos. Garantir que os testes abordem vários cenários de erro.
- Testar alternativas. Verificar se os mecanismos de alternativa funcionam conforme o previsto.
- Automatizar os testes. Utilizar pipelines CI/CD para automatizar e garantir um tratamento de erro robusto.
Cenários do Mundo Real
Cenário 1: Sistema de Processamento de Pagamentos
Tratar erros durante o processamento de pagamentos:
- Classes de erro personalizadas. Utilize classes como
CardValidationError
,PaymentGatewayError
. - Lógica de retentativas. Implementar retentativas para problemas relacionados à rede.
- Registro centralizado. Monitorar erros de pagamento e resolver problemas prontamente.
Cenário 2: Aplicações de Processamento de Dados
Gerenciar erros no processamento de dados:
- Degradação graciosa. Fornecer dados parciais ou visualizações alternativas.
- Dados de fallback. Use 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 abraçar erros personalizados, tratamento centralizado, propagação e testes robustos. Implementar essas técnicas permite que você construa aplicativos resilientes que oferecem uma experiência de usuário perfeita, mesmo quando algo dá errado.
Leitura adicional
- “JavaScript: The Good Parts” por Douglas Crockford
- “You Don’t Know JS: Async & Performance” por Kyle Simpson
- Documentação da Web MDN: Tratamento de Erros
Source:
https://dzone.com/articles/advanced-error-handling-in-javascript-custom-error