← Назад к вопросам

Как правильно обрабатывать uncaught exceptions и unhandled rejections в Node.js?

3.0 Senior🔥 291 комментариев
#Node.js и JavaScript#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка uncaught exceptions и unhandled rejections

В Node.js необработанные исключения и отклоненные промисы могут привести к аварийному завершению процесса и потере данных. Правильная обработка этих ситуаций критична для production-приложений.

Uncaught Exceptions

Uncaught exception возникает, когда синхронное исключение выбрасывается вне блока try/catch:

function processData(data: unknown) {
  const parsed = JSON.parse(data as string);
  return parsed.value.nested.prop;
}

// setTimeout callback - нет обертки try/catch
setTimeout(() => {
  processData("invalid json"); // uncaught exception!
}, 1000);

Обработка через process events

process.on("uncaughtException", (error: Error, origin: string) => {
  console.error("Uncaught Exception:", {
    message: error.message,
    stack: error.stack,
    origin
  });

  // Отправить в систему мониторинга
  logger.fatal("Uncaught exception", { error });

  // ВАЖНО: после uncaughtException процесс в неопределенном состоянии
  gracefulShutdown().then(() => process.exit(1));
});

process.on("unhandledRejection", (reason: unknown, promise: Promise<unknown>) => {
  console.error("Unhandled Rejection:", reason);

  logger.error("Unhandled rejection", {
    reason: reason instanceof Error ? reason.message : String(reason)
  });
});

Почему нельзя просто продолжить работу

После uncaughtException состояние приложения непредсказуемо:

  • Обработчик мог прерваться на середине записи в БД
  • Мьютексы и локи могут остаться захваченными
  • Буферы могут быть в inconsistent состоянии

Документация Node.js явно предупреждает: после uncaughtException единственное безопасное действие - залогировать ошибку и завершить процесс.

Unhandled Rejections

// Пример 1: забыли await или catch
async function fetchData() {
  const response = await fetch("https://api.example.com/data");
  if (!response.ok) throw new Error("API error");
  return response.json();
}

fetchData(); // unhandled rejection если ошибка!
// Правильно: fetchData().catch(handleError);

// Пример 2: ошибка внутри .then() без .catch()
Promise.resolve()
  .then(() => { throw new Error("oops"); });
  // нет .catch() -> unhandled rejection

Стратегия для production

1. Первый уровень - локальная обработка:

// Express error middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  logger.error("Request error", {
    error: err.message,
    url: req.url,
    method: req.method
  });
  res.status(500).json({ error: "Internal Server Error" });
});

// Обертка для async route handlers
function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) {
  return (req: Request, res: Response, next: NextFunction) => {
    fn(req, res, next).catch(next);
  };
}

app.get("/users", asyncHandler(async (req, res) => {
  const users = await userService.findAll();
  res.json(users);
}));

2. Второй уровень - глобальные обработчики:

process.on("uncaughtException", fatalHandler);
process.on("unhandledRejection", fatalHandler);

3. Третий уровень - менеджер процессов:

PM2, systemd или Docker restart policy автоматически перезапустят упавший процесс.

Мониторинг и алертинг

import * as Sentry from "@sentry/node";

Sentry.init({ dsn: SENTRY_DSN });

process.on("uncaughtException", (error) => {
  Sentry.captureException(error);
  Sentry.flush(2000).then(() => process.exit(1));
});

Рекомендации

  • Всегда используйте .catch() для промисов или try/catch для async/await
  • Никогда не подавляйте ошибки (пустой catch)
  • Используйте asyncHandler обертку для Express маршрутов
  • Настройте Sentry или аналог для отслеживания ошибок
  • Пусть процесс падает и перезапускается - это безопаснее, чем работать в сломанном состоянии