Express.jsMiddleCoding

Как обрабатывать ошибки async/await в обработчиках маршрутов Express?

В Express 4 async-хэндлеры не ловят ошибки автоматически — нужно либо явный try/catch с next(err), либо обёртка asyncHandler; Express 5 поддерживает async нативно.

Обработка ошибок async/await в маршрутах Express

Express 4 не умеет перехватывать ошибки из async-функций автоматически — необработанный rejected Promise просто зависнет или упадёт с UnhandledPromiseRejection. Есть три рабочих подхода.

1. Явный try/catch с передачей в next

import express, { Request, Response, NextFunction } from 'express';

const router = express.Router();

router.get('/users/:id', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
    if (!user.rows.length) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.json(user.rows[0]);
  } catch (err) {
    next(err); // передаём в error middleware
  }
});

Это самый явный способ — понятно, что происходит, и нет магии. Минус: много шаблонного кода при большом числе маршрутов.

2. Обёртка asyncHandler

Создаём утилиту, которая оборачивает async-хэндлер и автоматически пробрасывает ошибки в next:

// utils/asyncHandler.ts
import { Request, Response, NextFunction, RequestHandler } from 'express';

type AsyncFn = (req: Request, res: Response, next: NextFunction) => Promise<unknown>;

export const asyncHandler = (fn: AsyncFn): RequestHandler =>
  (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
// routes/users.ts
import { asyncHandler } from '../utils/asyncHandler';

router.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await userService.findById(req.params.id);
  res.json(user);
}));

Такой же паттерн предоставляет популярный пакет express-async-errors — он патчит сам Express и делает это глобально.

3. Express 5 (нативная поддержка)

В Express 5 (npm install express@^5) async-хэндлеры поддерживаются из коробки: отклонённый Promise автоматически вызывает next(err) без каких-либо обёрток.

// Express 5 — работает нативно
app.get('/items', async (req, res) => {
  const items = await itemService.getAll(); // если бросит — Express сам поймает
  res.json(items);
});

Error middleware (обязателен в любом случае)

// app.ts — регистрируется последним, ПОСЛЕ всех маршрутов
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  const status = (err as any).status ?? 500;
  res.status(status).json({
    error: process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message,
  });
});

Подводные камни

  • Забыть вызвать next(err) — без него Express продолжит выполнение или зависнет с таймаутом.
  • Express 4 не ловит async без обёртки — UnhandledPromiseRejection в Node.js приводит к падению процесса (или ignoring в старых версиях Node).
  • Error middleware без 4 аргументов не работает — Express определяет error middleware строго по сигнатуре (err, req, res, next); если убрать next, он перестаёт быть error middleware.
  • Регистрация error middleware до маршрутов — она должна быть последней в цепочке app.use(), иначе ошибки до неё не дойдут.
  • Утечка стека в production — никогда не отправляйте err.stack клиенту; фильтруйте по NODE_ENV.
  • Повторный вызов res.json() после next(err) — если в catch вызвать и res.json(), и next(err), будет «Cannot set headers after they are sent».
  • Ошибки внутри setTimeout/setInterval не перехватываются — async/await здесь не поможет; используйте отдельный try/catch или worker threads.

Common mistakes

  • Дает общий ответ про Node.js и не называет конкретные API Express.js.
  • Не объясняет, где в lifecycle находится ошибки async/await в route handlers.
  • Не разделяет validation, authorization, business logic и persistence.
  • Игнорирует ошибки, лимиты входных данных, observability и тестирование.

What the interviewer is testing

  • Может объяснить ошибки async/await в route handlers на примере кода.
  • Называет ключевые API: async (req, res), next(err).
  • Использует точные API Express.js, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics