A forma como lidamos com erros distingue um código amador de uma aplicação pronta para produção. Um erro não tratado pode derrubar seu servidor Node.js, deixar a interface do usuário em um estado inconsistente ou, pior, corromper dados silenciosamente. Um tratamento de erros eficaz implica em antecipar falhas, gerenciar imprevistos com elegância e fornecer feedback claro, tanto para os usuários quanto para os desenvolvedores, sobre a natureza do problema.
try/catch/finally: O Pilar Essencial para a Gestão de Erros
Tratamento Básico de Erros
try {
const data = JSON.parse(userInput);
processData(data);
} catch (error) {
// Lida com o erro
console.error("Falha ao analisar entrada:", error.message);
showUserMessage("Entrada inválida. Por favor, verifique seus dados.");
} finally {
// Sempre executa — código de limpeza
hideLoadingSpinner();
}
Compreendendo os Tipos de Erro Nativos do JavaScript
Tipos de Erro Nativos
// SyntaxError — JSON inválido, código malformado
JSON.parse("{invalid}");
// TypeError — tipo incorreto, tentando acessar propriedade de null/undefined
null.toString();
undefined.map(x => x);
// ReferenceError — usando uma variável não declarada
console.log(nonExistentVariable);
// RangeError — valor fora do intervalo permitido
new Array(-1);
// URIError — URI malformada
decodeURIComponent("%");
// Verifica o tipo do erro
try {
riskyOperation();
} catch (error) {
if (error instanceof TypeError) {
// Trata erros de tipo especificamente
} else if (error instanceof SyntaxError) {
// Trata erros de sintaxe
} else {
throw error; // Relança erros inesperados
}
}
Classes de Erro Personalizadas: Aprimorando a Comunicação e o Controle
Erros Personalizados
class AppError extends Error {
constructor(message, statusCode, code) {
super(message);
this.name = 'AppError';
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} não encontrado`, 404, 'NOT_FOUND');
this.name = 'NotFoundError';
}
}
class ValidationError extends AppError {
constructor(field, message) {
super(`Validação falhou: ${message}`, 400, 'VALIDATION_ERROR');
this.name = 'ValidationError';
this.field = field;
}
}
// Exemplo de Uso
function getUser(id) {
const user = db.findUser(id);
if (!user) throw new NotFoundError('User');
return user;
}
Gestão de Erros Assíncronos: Dominando Promises e Async/Await
Erros em Async/Await
// ✅ Correto — try/catch com async/await
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new AppError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
'HTTP_ERROR'
);
}
return await response.json();
} catch (error) {
if (error instanceof AppError) {
throw error; // Relança erros conhecidos
}
// Encapsula erros desconhecidos
throw new AppError('Erro de rede', 500, 'NETWORK_ERROR');
}
}
// ❌ Incorreto — rejeição de promise não tratada
async function bad() {
const data = await fetch('/api/data'); // Se isso lançar um erro, ele não será tratado!
}
Gerenciadores de Erros Globais: A Rede de Segurança Final da Aplicação
Captura Abrangente de Erros
// Navegador — captura erros não tratados
window.addEventListener('error', (event) => {
console.error('Erro não capturado:', event.error);
// Envia para o serviço de rastreamento de erros
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Rejeição de promise não tratada:', event.reason);
event.preventDefault(); // Previne o tratamento padrão do navegador
});
// Node.js — captura erros não tratados
process.on('uncaughtException', (error) => {
console.error('Exceção não capturada:', error);
process.exit(1); // Sai — o processo está em um estado desconhecido
});
process.on('unhandledRejection', (reason) => {
console.error('Rejeição não tratada:', reason);
// No Node 15+, isso encerra o processo por padrão
});
Middleware de Erros no Express.js: Centralizando e Padronizando Respostas
Gerenciador de Erros Centralizado
// Middleware de tratamento de erros (4 parâmetros)
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Erro interno do servidor';
// Registra o erro completo
console.error(`[${err.code}] ${err.message}`, err.stack);
// Envia resposta "limpa" ao cliente
res.status(statusCode).json({
error: {
message,
code: err.code || 'INTERNAL_ERROR',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
Boas Práticas Essenciais para um Tratamento de Erros Robusto
- **Nunca ignore erros silenciosamente:** Blocos `catch` vazios são uma das piores armadilhas, pois mascaram bugs e impedem a depuração. Sempre registre ou relance o erro.
- **Utilize classes de erro personalizadas:** Diferencie erros operacionais (como validação de entrada, recurso não encontrado) de erros de programação (bugs inesperados). Isso facilita a lógica de tratamento e a resposta ao usuário.
- **Sempre relance erros que você não consegue tratar:** Deixe que os erros "subam" na cadeia de chamadas até um manipulador capaz de processá-los adequadamente ou, em último caso, para um manipulador global.
- **Registre erros com contexto:** Um log útil inclui informações como ID do usuário, URL da requisição, dados de entrada relevantes e o stack trace completo. Isso é vital para diagnosticar a causa raiz.
- **Utilize um serviço de rastreamento de erros em produção:** Ferramentas como Sentry, Datadog ou LogRocket são indispensáveis para coletar, agregar e alertar sobre erros em tempo real, fornecendo insights valiosos.
- **Falhe de forma elegante:** Em vez de travar ou exibir uma página em branco, forneça ao usuário uma mensagem de erro compreensível e sugestões sobre o que fazer em seguida, melhorando a experiência do usuário.
Experimente Nossas Ferramentas Gratuitas de JavaScript
Depure e formate seu código JavaScript instantaneamente com facilidade.