Обработка ошибок в middleware
Обработчик ошибок — middleware с четырьмя аргументами (err, req, res, next), единое место для всех сбоев.
«Хорошее приложение не падает от ошибки — оно ловит её в одном месте и вежливо отвечает клиенту.»
Ошибки неизбежны: база недоступна, данные кривые, файл не найден. Без централизованной обработки придётся в каждом маршруте писать try/catch и руками формировать ответ. Express предлагает изящное решение — специальный error-handling middleware, через который проходят все ошибки приложения.
Особый middleware
Обработчик ошибок отличается от обычного middleware числом аргументов: их четыре, и первый — err:
// обычный middleware: (req, res, next)
// обработчик ошибок: (err, req, res, next)
app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.status || 500;
res.status(status).json({ error: err.message || 'Внутренняя ошибка' });
});Express узнаёт обработчик ошибок именно по четырём аргументам и вызывает его, когда где-то происходит ошибка. Он должен стоять последним, после всех маршрутов.
Как ошибка попадает в обработчик
Есть два пути. Первый — явный вызов next(err). Второй (в Express 5) — выброс ошибки из async-функции:
// Express 4: нужен try/catch и next(err)
app.get('/old', async (req, res, next) => {
try {
const data = await loadData();
res.json(data);
} catch (err) {
next(err); // вручную в обработчик ошибок
}
});
// Express 5: отклонённый промис уходит в обработчик САМ
app.get('/new', async (req, res) => {
const data = await loadData(); // если упадёт -- поймает Express 5
res.json(data);
});Это одно из главных улучшений Express 5: для async-обработчиков больше не нужен try/catch в каждом маршруте.
Поток обработки ошибки
запрос
|
v
[ обработчик ] -- throw / next(err) -->
|
обычные middleware ПРОПУСКАЮТСЯ |
v
[ (err, req, res, next) ]
|
v
ответ с кодом ошибкиСобственный класс ошибки в браузере
Стандартный Error не несёт HTTP-статуса. Создадим свой класс с кодом — обработчик ошибок потом его прочитает:
class HttpError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
function findUser(id) {
if (id !== 1) throw new HttpError(404, 'Пользователь не найден');
return { id: 1, name: 'Аня' };
}
try {
findUser(99);
} catch (e) {
console.log('Статус:', e.status); // 404
console.log('Текст: ', e.message); // Пользователь не найден
}Такой класс — основа аккуратной обработки ошибок: бросаешь new HttpError(404, ...), а один обработчик превращает это в правильный ответ.
Как работает под капотом
Внутри Express отделяет обычные слои от error-слоёв по длине списка аргументов функции. Когда возникает ошибка, Express перестаёт вызывать обычные middleware и ищет следующий слой с четырьмя аргументами, передавая ему err. Если такого нет, срабатывает встроенный обработчик по умолчанию, который в проде скрывает детали ошибки.
Частые ошибки
- Поставить обработчик не последним. Если он выше маршрутов, ошибки до него не дойдут.
- Забыть про async в Express 4. Без try/catch отклонённый промис не попадёт в обработчик и станет unhandled rejection.
- Показывать стектрейс клиенту. В проде отдавай общее сообщение, а детали — только в лог.
Best practices
- Заведи один централизованный обработчик ошибок в конце приложения.
- Используй классы ошибок со статусом для осмысленных ответов.
- Логируй полную ошибку на сервере, клиенту отдавай безопасное сообщение.
Итоги
Error-handling middleware с четырьмя аргументами — единая точка обработки сбоев. В Express 5 async-ошибки попадают в него автоматически. Это завершает раздел про middleware; дальше соберём из всего изученного полноценный REST API.
Асинхронные ловушки и 404
Даже в Express 5 автоматический проброс работает только для ошибок внутри самого обработчика. Если ты запускаешь асинхронную операцию "в сторону" (например, в колбэке setTimeout без await), её отказ Express не поймает — это будет unhandled rejection уровня процесса. Поэтому всё, что относится к ответу, держи в основной async-цепочке с await. Отдельно стоит ситуация "маршрут не найден": это не ошибка, а нормальный исход, поэтому 404 обычно отдают не через обработчик ошибок, а отдельным завершающим middleware. Разделяй в голове две вещи: ожидаемые ответы вроде 404 и непредвиденные сбои, которые ловит error-handler.