O que são erros não tratados no Node.js?
Erros não tratados em Node.js são falhas no código que não são capturadas e gerenciadas adequadamente com blocos try/catch ou métodos de tratamento de erro. Isso pode causar o desligamento da aplicação, comportamento inesperado ou perda de dados. No entanto, o Node.js oferece ferramentas poderosas para lidar com esses erros de forma eficaz e garantir que a aplicação continue funcionando sem interrupções.
Neste guia, vamos explorar como capturar e tratar erros não tratados em uma aplicação Node.js, garantindo que sua aplicação seja robusta, confiável e fácil de manter.
1. Capturando erros não tratados no processo principal
No Node.js, qualquer erro não tratado em uma função assíncrona ou Promise pode ser capturado globalmente. Para isso, o process do Node.js oferece os eventos uncaughtException e unhandledRejection, que permitem detectar erros não tratados no seu código e tratar de forma apropriada.
Usando o evento uncaughtException
O evento uncaughtException é acionado sempre que ocorre um erro não tratado em qualquer parte da sua aplicação.
process.on('uncaughtException', (err) => {
console.error('Erro não tratado:', err);
process.exit(1); // Finaliza a aplicação após o erro
});
// Exemplo de erro não tratado
throw new Error('Erro inesperado');
Neste exemplo, sempre que ocorrer um erro não tratado, o evento uncaughtException será acionado, registrando o erro no console e finalizando a aplicação com o código de saída 1, indicando uma falha.
Embora esse evento seja útil, o uso de uncaughtException não é recomendado para capturar erros em tempo de execução em grande escala, pois ele pode mascarar problemas de fundo em sua aplicação. O ideal é sempre tratar os erros diretamente no código com try/catch.
Usando o evento unhandledRejection
O evento unhandledRejection captura rejeições de Promises que não são tratadas com um .catch(). Isso ocorre quando uma Promise é rejeitada e você não faz nada para capturar o erro.
process.on('unhandledRejection', (reason, promise) => {
console.error('Promise rejeitada sem captura:', promise, 'Razão:', reason);
process.exit(1); // Finaliza a aplicação após o erro
});
// Exemplo de rejeição de Promise sem captura
Promise.reject('Erro na promise');
Neste código, o evento unhandledRejection é acionado quando uma Promise é rejeitada sem ser tratada adequadamente. Assim como no uncaughtException, o processo será encerrado após o erro ser capturado.
2. Tratando erros em funções assíncronas
Em funções assíncronas que utilizam async/await, é importante capturar erros com blocos try/catch para evitar que erros não tratados causem falhas na execução.
Exemplo de tratamento com try/catch:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (err) {
console.error('Erro ao buscar dados:', err);
}
}
fetchData();
Neste exemplo, usamos try/catch para capturar qualquer erro que ocorra ao realizar uma requisição assíncrona. Isso evita que erros inesperados interrompam a execução da aplicação.
3. Utilizando o domains para capturar erros em múltiplos callbacks
O módulo domain permite que você capture erros de callbacks assíncronos em várias funções, facilitando o tratamento de erros de forma centralizada. No entanto, o domain está depreciado e seu uso não é recomendado para novos projetos. Se você já utiliza domains, é importante considerar alternativas para tratar erros, como Promises e async/await.
Exemplo com domain (deprecado):
const domain = require('domain');
const d = domain.create();
d.on('error', (err) => {
console.error('Erro capturado pelo domain:', err);
});
d.run(() => {
// Código que pode lançar erro
throw new Error('Erro dentro do domínio');
});
Embora funcione para capturar erros dentro do domínio, o domain não é mais recomendado para novos projetos, devido à sua complexidade e à disponibilidade de outras opções mais modernas.
4. Registrando erros em arquivos de log
Uma boa prática é registrar os erros em arquivos de log, para que você possa acompanhar a ocorrência de erros e analisar sua aplicação de maneira eficaz. Usando bibliotecas como Winston ou Morgan, você pode facilmente registrar erros em arquivos e consultar esses logs posteriormente.
Exemplo com Winston para registrar erros:
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
transports: [
new winston.transports.File({ filename: 'errors.log' })
],
});
// Exemplo de erro que será registrado
try {
throw new Error('Erro capturado');
} catch (err) {
logger.error('Erro capturado: ' + err.message);
}
Este exemplo usa Winston para gravar erros em um arquivo de log. Isso é útil para auditoria, monitoramento e análise dos problemas da sua aplicação.
5. Conclusão
Capturar e tratar erros não tratados é essencial para garantir que sua aplicação Node.js seja robusta e estável. Usando os eventos uncaughtException e unhandledRejection, você pode monitorar erros globais e tomar ações apropriadas, como registrar os erros ou finalizar o processo. No entanto, sempre que possível, use try/catch em funções assíncronas e trate as Promessas rejeitadas diretamente para evitar falhas.
Lembre-se de registrar todos os erros significativos em arquivos de log ou sistemas de monitoramento para facilitar a análise e a resolução de problemas em produção.
Captura e monitoramento de erros não tratados em Node.js
Capturar erros não tratados é uma parte fundamental para a criação de uma aplicação Node.js resiliente. A ferramenta de tratamento de erros permite que a aplicação continue funcionando mesmo quando ocorrem falhas. Usar uncaughtException e unhandledRejection pode ajudar a lidar com erros inesperados de forma global, mas é importante tratar os erros de maneira mais granular com try/catch e Promises.
Além disso, manter um sistema de logs adequado ajuda a monitorar erros e analisar o comportamento da aplicação em produção, facilitando a depuração de problemas e melhorando a experiência do usuário.
Algumas aplicações:
- Capturar erros de banco de dados não tratados e registrar falhas.
- Monitorar requisições HTTP para detectar falhas e problemas de conectividade.
- Usar logs para registrar erros em produção e realizar auditorias de segurança.
- Simular falhas de rede ou APIs externas e testar como sua aplicação lida com isso.
- Utilizar testes de integração e unitários para verificar o tratamento de erros em diferentes partes da aplicação.
Dicas para quem está começando
- Sempre trate erros com try/catch para evitar que erros inesperados interrompam sua aplicação.
- Use o uncaughtException e unhandledRejection para capturar erros globais e garantir a estabilidade do sistema.
- Considere usar uma ferramenta de monitoramento de erros em produção para detectar falhas rapidamente.
- Registre todos os erros críticos em arquivos de log para facilitar a análise posterior.
- Evite deixar erros não tratados no código de produção. Trate-os o mais cedo possível.
Contribuições de Ricardo Vasconcellos