В асинхронном обработчике Express ошибка прерывает выполнение middleware только если она не перехвачена. Если функция возвращает Promise или использует async/await, ошибка считается необработанной, пока вы явно не передадите её в next(err) или не завершите ответ через res.send(). Иначе сервер выдаст 500 Internal Server Error с сырым стеком трассировки.

Исключение в асинхронном обработчике Express: базовый принцип и место ловушки

В синхронном коде ошибка останавливает выполнение сразу. В асинхронном — она остаётся «висеть» внутри Promise, пока не будет перехвачена. Если вы забыли обработать ошибку в async-функции, Express продолжит искать следующий middleware, но поток уже прерван, и сервер выдаст стандартную ошибку 500.

Ловушка часто возникает при использовании async/await без блока try/catch или при возврате Promise.reject() без обработки. В обоих случаях ошибка считается необработанной на этом уровне.

Пример условного кода с ошибкой:

Структура try/catch внутри async-функции: как правильно оборвать поток

Правильный паттерн — обернуть асинхронную логику в try/catch и передать ошибку в next(err) или завершить ответ. Это сигнализирует Express, что ошибка обработана на этом уровне.

Если вы передадите err в next(), управление перейдёт к следующему middleware или глобальному обработчику ошибок. Если вы завершите ответ через res.send() или res.json(), цепочка middleware прерывается, и дальнейшие обработчики не вызовутся.

Условный пример корректной обработки:

Обработка ошибок на уровне middleware: использование next(err)

Middleware в Express — это функции с сигнатурой (req, res, next). Если функция асинхронна, она должна явно передать ошибку через next(err), иначе она останется необработанной.

next(err) передаёт управление дальше. Это позволяет централизованно собирать ошибки или логировать их перед выводом пользователю.

Важно: не используйте res.send() внутри try/catch, если хотите передать ошибку дальше. Используйте его только для завершения ответа с сообщением об ошибке.

Глобальный обработчик ошибок (error-handling middleware): перехват необработанных исключений

Глобальный обработчик ошибок должен быть специфичным: он принимает четыре параметра (err, req, res, next). Это отличает его от обычных middleware.

Он вызывается только после всех маршрутов и middleware. Если ошибка не перехвачена ни в одном обработчике, Express автоматически вызывает этот глобальный обработчик.

Пример структуры глобального обработчика:

Различие между синхронными и асинхронными ошибками в Express

Синхронная ошибка (например, throw в обычной функции) останавливает выполнение сразу. Асинхронная ошибка (в async-функции или Promise) остаётся внутри потока, пока не будет перехвачена.

Express автоматически перехватывает синхронные ошибки в middleware, но асинхронные требуют явного next(err).

Если вы используете библиотеки с асинхронными операциями (например, запросы к базе данных), всегда оборачивайте их в try/catch или используйте .catch().

Условный пример синхронной ошибки:

Логирование ошибок: предотвращение утечки чувствительных данных

При логировании ошибок не выводите req.body, req.params или req.headers напрямую. Это может раскрыть пароли, токены или личные данные пользователей.

Используйте безопасные методы логирования: фильтруйте чувствительные поля или используйте библиотеки для маскирования данных.

Пример безопасного логирования:

Возврат пользовательских сообщений vs технические детали ошибки

Пользователь должен видеть понятное сообщение, например «Не удалось обработать запрос», а не стек трассировки с путями к файлам и номерами строк.

Технические детали оставляйте в логах сервера. Для пользователя возвращайте только код ошибки и краткое описание.

Пример формирования ответа:

Ограничения обработки ошибок в цепочке middleware

Если вы завершите ответ через res.send() или res.json(), дальнейшие middleware не вызовутся. Это нормально, если ошибка уже обработана.

Если вы передадите err в next(), но следующий middleware тоже завершит ответ с ошибкой, последняя ошибка будет видна пользователю.

Глобальный обработчик ошибок вызывается только один раз для каждой ошибки. Если вы захотите изменить поведение после первой обработки, это нужно делать явно в коде.

Вопросы и ответы

Что будет, если забыть передать err в next() в async-функции?

Ошибка останется необработанной, и Express выдаст 500 Internal Server Error с сырым стеком трассировки. Это может раскрыть внутреннюю структуру проекта.

Можно ли использовать try/catch в синхронном middleware?

Да, но только если функция не асинхронна. В синхронном коде ошибка останавливает выполнение сразу, и её можно перехватить без next(err).

Как отличить глобальный обработчик ошибок от обычного middleware?

Глобальный обработчик принимает четыре параметра (err, req, res, next). Обычное middleware — только три (req, res, next).

По теме

// Условный пример асинхронного обработчика с try/catch
app.get('/api/data', async (req, res, next) => {
  try {
    const data = await fetchData(req.params.id);
    res.json({ data });
  } catch (err) {
    // Передаём ошибку дальше или завершаем ответ
    next(err); 
  }
});

// Глобальный обработчик ошибок
app.use((err, req, res, next) => {
  console.error('Ошибка:', err.message);
  res.status(500).json({ message: 'Не удалось обработать запрос' });
});