Tratamento de Erros em JavaScript: try/catch e Além: Nível Avançado
O tratamento de erros não é apenas uma proteção contra a queda da aplicação, mas um aspecto fundamental para construir sistemas confiáveis e tolerantes a falhas em JavaScript e Node.js. O uso básico de try/catch é familiar para todo desenvolvedor, mas em projetos reais, especialmente no lado do servidor, surgem muitas nuances: fluxos assíncronos, cadeias de promises, falhas globais e a necessidade de erros customizados estruturados. Neste artigo, vamos explorar técnicas avançadas de tratamento de erros que ajudarão você a escrever um código mais limpo e previsível em Node.js.
1. Mergulho Profundo em try/catch e Assincronicidade
O clássico try/catch é síncrono. Ele captura perfeitamente erros dentro de funções comuns, mas falha diante de callbacks assíncronos ou promises sem await. Em Node.js, onde 90% do código é assíncrono, isso frequentemente se torna a causa de exceções "engolidas".
Erro: try/catch não captura o erro em setTimeout
function dangerousAsync() { setTimeout(() => { throw new Error('Falha no timer!'); }, 1000);}
try { dangerousAsync();} catch (e) { console.log('Capturado:', e.message); // Este código NÃO será executado}Neste exemplo, o erro é lançado fora do contexto do try, e o processo Node.js será encerrado abruptamente. A abordagem correta é usar promises ou async/await.
Solução: encapsulando o código assíncrono em uma Promise
function safeAsync() { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Falha no timer!')); }, 1000); });}
(async () => { try { await safeAsync(); } catch (e) { console.log('Capturado:', e.message); // Será executado após 1 segundo }})();A regra principal: encapsule qualquer operação assíncrona em uma promise e use await dentro de try/catch. Isso garante que o erro será capturado no contexto correto.
2. Classes de Erro Customizadas: Semântica e Contexto
Em vez de lançar new Error('Algo deu errado'), crie uma hierarquia de suas próprias classes de erro. Isso permite que, no nível do manipulador, você entenda o tipo de falha e reaja de forma adequada (por exemplo, registrar em log, enviar para monitoramento ou retornar um status HTTP específico ao usuário).
class AppError extends Error { constructor(message, statusCode = 500) { super(message); this.name = this.constructor.name; this.statusCode = statusCode; Error.captureStackTrace(this, this.constructor); }}
class ValidationError extends AppError { constructor(message) { super(message, 400); }}
class DatabaseError extends AppError { constructor(message, originalError) { super(message, 503); this.originalError = originalError; // Salva o erro original para depuração }}
// Usofunction findUser(id) { if (!id) throw new ValidationError('ID do usuário é obrigatório'); // ... lógica que pode lançar DatabaseError}Vantagens desta abordagem:
- Legibilidade: o nome da classe já indica a natureza do problema.
- Roteamento: no manipulador global, você pode verificar
instanceofe escolher a estratégia de resposta. - Campos adicionais: você pode adicionar quaisquer dados (
statusCode,originalError,userId, etc.).
3. Manipuladores Globais de Erro em Node.js
Mesmo com o controle mais rigoroso, erros não tratados podem vazar. Em Node.js, existem dois eventos-chave para capturá-los: uncaughtException e unhandledRejection