Tratamento de Erros em JavaScript: try/catch e muito mais
O tratamento de erros é uma das tarefas principais no desenvolvimento com JavaScript, especialmente no ambiente Node.js, onde a estabilidade da aplicação servidora impacta diretamente a experiência do usuário. Exceções não tratadas podem levar à queda de todo o processo e à perda de dados. Neste artigo, abordaremos não apenas o bloco clássico try/catch, mas também técnicas avançadas: trabalho com finally, criação de tipos de erro personalizados, tratamento de erros assíncronos e melhores práticas para Node.js.
1. Fundamentos: try, catch e finally
O bloco try/catch é o mecanismo fundamental para capturar exceções síncronas. Se ocorrer um erro no bloco try, a execução é imediatamente transferida para o catch. O bloco finally é executado sempre — independentemente de ter ocorrido um erro ou não. Isso é útil para liberar recursos (fechar arquivos, conexões).
const fs = require('fs');
function readConfig(path) { try { const data = fs.readFileSync(path, 'utf8'); console.log('Configuração carregada'); return JSON.parse(data); } catch (err) { console.error('Erro ao ler a configuração:', err.message); return null; } finally { console.log('Tentativa de leitura concluída'); }}
const config = readConfig('./config.json');console.log(config); // null em caso de erroImportante: o catch captura apenas erros que ocorreram no try. Erros no catch ou finally permanecem não tratados.
2. Tipos de Erro em JavaScript e Node.js
Em JavaScript, existem vários tipos de erro embutidos, cada um herdando de Error:
- SyntaxError — erro de sintaxe na análise do código.
- ReferenceError — referência a uma variável inexistente.
- TypeError — tipo de dado inválido (por exemplo, chamar algo que não é uma função).
- RangeError — valor fora do intervalo permitido.
- URIError — erro ao trabalhar com URI (encodeURI/decodeURI).
No Node.js, são adicionados AssertionError (do módulo assert) e erros de sistema (EACCES, ENOENT, etc.) do módulo fs, que contêm o código do erro na propriedade code.
try { require('fs').readFileSync('/nonexistent');} catch (err) { if (err.code === 'ENOENT') { console.log('Arquivo não encontrado'); } else { console.log('Outro erro:', err.message); }}3. Criação de Erros Personalizados
Para um controle mais preciso sobre a lógica de tratamento, é recomendável criar classes de erro próprias, estendendo Error. Isso permite distinguir erros de lógica de negócio de falhas de sistema.
class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; }}
class DatabaseError extends Error { constructor(message, query) { super(message); this.name = 'DatabaseError'; this.query = query; }}
function saveUser(user) { if (!user.email) { throw new ValidationError('Email é obrigatório', 'email'); } // simulação de erro de BD throw new DatabaseError('Timeout de conexão', 'INSERT INTO users');}
try { saveUser({ name: 'Ivan' });} catch (err) { if (err instanceof ValidationError) { console.log(`Campo ${err.field}: ${err.message}`); } else if (err instanceof DatabaseError) { console.error(`Erro de BD (${err.query}): ${err.message}`); // nova tentativa ou registro em log } else { console.error('Erro desconhecido:', err); }}O uso de instanceof permite construir cadeias de manipuladores sem recorrer à verificação de códigos de string.