Uma unhandled rejection acontece quando uma Promise é rejeitada e nenhum handler de erro é associado a tempo. Em Node.js, isso indica que uma operação assíncrona falhou fora do fluxo de tratamento esperado. O resultado pode ser encerramento do processo, aviso, estado inconsistente ou perda de contexto, conforme a versão e a configuração.
A solução não é adicionar um listener global e continuar normalmente. O objetivo deve ser encontrar a Promise sem tratamento, preservar diagnóstico e encerrar de forma segura quando a política considerar a falha fatal.
Exemplo simples
async function loadConfiguration() {
throw new Error('Configuração inválida');
}
loadConfiguration();A Promise retornada não foi aguardada nem recebeu catch.
Tratamento correto
try {
await loadConfiguration();
} catch (error) {
logger.fatal({ err: error }, 'Falha ao carregar configuração');
throw error;
}No bootstrap, a falha normalmente deve impedir o servidor de iniciar.
Fire-and-forget
Este padrão é perigoso:
sendAnalytics(event);Mesmo quando o resultado não é necessário, a rejeição precisa ser observada:
void sendAnalytics(event).catch((error) => {
logger.warn({ err: error }, 'Falha ao enviar analytics');
});Use fire-and-forget somente para tarefa realmente opcional. Para trabalho importante, use fila durável.
Await esquecido
async function controller(req, res) {
service.create(req.body);
res.status(202).end();
}Se service.create falha, a resposta já foi enviada. Use await ou enfileire explicitamente.
Array.forEach assíncrono
items.forEach(async (item) => {
await processItem(item);
});forEach ignora Promises. Alternativas:
for (const item of items) {
await processItem(item);
}
await Promise.all(items.map(processItem));Promise.all inicia tudo ao mesmo tempo. Para listas grandes, limite concorrência.
map sem Promise.all
const tasks = items.map(async (item) => processItem(item));
Se tasks nunca é aguardado, rejeições podem ficar sem tratamento.
setTimeout assíncrono
setTimeout(async () => {
await operation();
}, 1000);O timer não observa a Promise. Trate dentro:
setTimeout(() => {
void operation().catch((error) => {
logger.error({ err: error }, 'Tarefa agendada falhou');
});
}, 1000);EventEmitter
Callbacks assíncronos em eventos também precisam de catch:
emitter.on('message', (message) => {
void processMessage(message).catch(handleMessageError);
});Uma fila real deve controlar ack, retry e dead letter.
Express
Em frameworks, garanta que rejeições de handlers cheguem ao middleware de erro. Dependendo da versão, use wrapper:
const asyncHandler = (handler) => (req, res, next) => {
Promise.resolve(handler(req, res, next)).catch(next);
};Listener global
process.on('unhandledRejection', (reason, promise) => {
logger.fatal({
err: reason,
}, 'Promise rejeitada sem tratamento');
beginFatalShutdown('unhandledRejection');
});O listener serve para diagnóstico e shutdown. Não use para “resolver” a Promise ou continuar atendendo.
Por que não continuar
A rejeição pode ter ocorrido durante atualização parcial, inicialização, transação, lock ou processamento de mensagem. O código que deveria compensar não foi executado. O estado do processo pode ser desconhecido.
Política de processo
Defina uma política explícita:
- registrar como fatal;
- marcar readiness como false;
- parar de aceitar trabalho;
- drenar dentro de prazo curto;
- fechar recursos;
- encerrar com código não zero;
- permitir reinício pelo supervisor.
uncaughtException
Unhandled rejection e uncaught exception são sinais diferentes, mas ambos indicam falha fora do tratamento normal. Centralize o shutdown fatal sem registrar duas vezes.
process.on('uncaughtException', (error, origin) => {
logger.fatal({ err: error, origin }, 'Exceção não capturada');
beginFatalShutdown(origin);
});Idempotência do shutdown
let fatalShutdownPromise;
function beginFatalShutdown(reason) {
if (!fatalShutdownPromise) {
fatalShutdownPromise = shutdown({ reason, exitCode: 1 });
}
return fatalShutdownPromise;
}Vários eventos podem ocorrer durante a falha. Execute o fluxo uma vez.
Não faça trabalho complexo no handler
O processo está em situação anormal. Evite chamadas externas longas, uploads grandes e lógica de negócio. Faça logging mínimo e limpeza limitada.
Monitor externo
Use systemd, Kubernetes, PM2 ou outro supervisor para reiniciar. O próprio processo não deve tentar reiniciar-se.
Crash loop
Se o bug ocorre no startup, o supervisor pode reiniciar continuamente. Use backoff, limite de reinícios, alertas e rollback.
Ambiente de teste
Configure testes para falhar quando existe rejeição não tratada. Runners modernos normalmente fazem isso, mas confirme a política.
Helper para Promises ignoradas
function runDetached(promise, context) {
void promise.catch((error) => {
logger.error({ err: error, ...context }, 'Tarefa destacada falhou');
});
}Nomear a intenção deixa claro que a tarefa é destacada. Ainda assim, não use para trabalho crítico.
eslint
Regras de TypeScript ESLint podem detectar Promises flutuantes e awaits incorretos. Exemplos de categorias úteis:
- no-floating-promises;
- no-misused-promises;
- require-await;
- return-await conforme política.
Adote gradualmente e corrija exceções conscientemente.
TypeScript
Tipar retornos como Promise ajuda, mas TypeScript não obriga await. Linters complementam o compilador.
void explícito
void optionalTask().catch(handleOptionalError);O operador void sinaliza que o resultado é ignorado intencionalmente, sem dispensar o catch.
Promise constructor
Evite criar Promise desnecessariamente ao envolver função async. Erros dentro de callbacks precisam chamar reject.
return new Promise((resolve, reject) => {
legacyApi((error, result) => {
if (error) {
reject(error);
return;
}
resolve(result);
});
});Callbacks que lançam
Um throw dentro de callback assíncrono não é capturado por try/catch externo:
try {
setTimeout(() => {
throw new Error('Falha');
}, 0);
} catch {
// não captura
}Trate dentro do callback ou converta para Promise observada.
Promise.race
Uma Promise que perde a corrida continua executando. Se ela falhar, a Promise original já possui handlers internos de race, mas o recurso continua. Use AbortController para cancelar o perdedor.
Timeout ingênuo
await Promise.race([
operation(),
timeoutPromise(5000),
]);Isso não cancela operation. Passe signal.
Streams
Pipeline baseado em Promise precisa ser aguardado:
await pipeline(source, transform, destination);Iniciar sem await pode gerar rejeição após a função retornar.
Worker Threads
Erros e exits de workers precisam ser observados. Uma Promise que representa a tarefa deve rejeitar e ser aguardada.
Processos filhos
Trate error, close e exit code. Não presuma que iniciar o processo significa sucesso.
Mensageria
Um handler que retorna Promise deve ser aguardado pela biblioteca. Confirme o contrato. Ack antes da conclusão perde mensagens em falha.
Logs
Inclua stack, cause, origin, versão e request/trace ID quando disponíveis. Não registre a Promise inteira, que raramente ajuda e pode conter referências.
Process Reports
Em falhas fatais, um Process Report pode capturar contexto. Gere com cuidado e limite para evitar disco cheio em crash loop.
Métricas
Unhandled rejection deve ser rara. Conte eventos e alerte imediatamente. Também acompanhe reinícios e crash loops.
Testes de integração
Crie um processo filho que dispara a falha e confirme exit code, log e shutdown. Não derrube o runner principal.
Erros comuns
- adicionar listener e continuar;
- ignorar Promise;
- usar forEach async;
- não tratar timer async;
- responder antes de await;
- fazer trabalho complexo no handler;
- reiniciar dentro do processo;
- não limitar crash loop;
- não usar linter;
- confundir timeout com cancelamento.
Fluxo recomendado
Aguarde Promises, trate tarefas destacadas explicitamente, habilite lint e considere rejeição não tratada uma falha fatal. Combine com Tratamento de Erros, Pino, Graceful Shutdown e Process Reports.
Consulte a documentação oficial de unhandledRejection e a referência de uncaughtException.


