Introdução: Por que o tratamento de erros é a base de um código confiável
Erros no código são inevitáveis. Eles surgem devido a dados incorretos do usuário, falhas de rede, ausência de arquivos no servidor ou simples erros de digitação do desenvolvedor. No ambiente Node.js, onde o código é executado de forma assíncrona e frequentemente interage com recursos externos (bancos de dados, sistema de arquivos, API), um erro não tratado pode levar à queda de todo o processo. Um tratamento adequado de erros em JavaScript transforma uma aplicação frágil em um serviço robusto. Neste artigo, abordaremos todos os mecanismos principais: desde o clássico try/catch até a criação de classes de erro personalizadas e o trabalho com código assíncrono.
1. Fundamentos: try, catch, finally e throw
O bloco básico para tratamento de exceções em JavaScript é a construção try...catch...finally. Ela permite "capturar" um erro que ocorreu em uma seção "perigosa" do código e reagir corretamente a ele, sem interromper a execução de todo o programa.
Sintaxe e funcionamento do try/catch
O código que pode lançar uma exceção é colocado no bloco try. Se ocorrer um erro, o controle é imediatamente transferido para o bloco catch, onde o objeto de erro está disponível. Se não houver erro, o bloco catch é ignorado.
try { const data = JSON.parse('{"name": "Ana"}'); // JSON válido console.log(data.name); // Exibirá: Ana} catch (error) { console.error('Erro de parsing:', error.message);}// A execução continuaAgora, simulando um erro:
try { const data = JSON.parse('json inválido'); // Erro! console.log(data.name);} catch (error) { console.error('Erro de parsing:', error.message); // Saída: Erro de parsing: Unexpected token 'j', ... is not valid JSON}console.log('O programa continua!'); // Este código será executadoBloco finally: código que sempre será executado
O bloco finally é executado independentemente de ter ocorrido um erro ou não. Ele é ideal para liberar recursos (fechar arquivos, conexões de banco de dados) ou limpar timers.
let recurso = null;try { recurso = adquirirRecurso(); // Obtém o recurso recurso.fazerAlgo(); // Operação perigosa} catch (error) { console.error('Erro:', error);} finally { if (recurso) { recurso.fechar(); // Sempre fecha o recurso console.log('Recurso liberado.'); }}Operador throw: criando seus próprios erros
Você mesmo pode "lançar" um erro usando throw. Isso é útil para validar dados de entrada ou interromper a execução quando a lógica de negócio é violada. Como argumento, você pode passar qualquer coisa, mas a melhor prática é usar o objeto Error ou seus descendentes.
function dividir(a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw new Error('Os argumentos devem ser números'); } if (b === 0) { throw new Error('Divisão por zero não é permitida'); } return a / b;}
try { console.log(dividir(10, 0));} catch (error) { console.error('Erro:', error.message); // Erro: Divisão por zero não é permitida}2. Técnicas avançadas: Classes de erro personalizadas e tipagem
Em projetos grandes, usar apenas o Error padrão não é suficiente. É necessário diferenciar erros de rede, validação, autorização e outros. Para isso, criam-se classes personalizadas que estendem o Error nativo.
Criação de erros personalizados
class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; // Nome único para verificação }}
class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = 'NetworkError'; this.statusCode = statusCode; }}
function buscarUsuario(id) { if (!id) { throw new Valida