Обработка ошибок¶
Обработка ошибок — это то, как Express перехватывает и обрабатывает ошибки, возникающие как в синхронном, так и в асинхронном коде. В Express есть встроенный обработчик ошибок, поэтому для старта не обязательно писать свой.
Перехват ошибок¶
Важно обеспечить, чтобы Express перехватывал все ошибки, возникающие при выполнении route handler и middleware.
Ошибки в синхронном коде внутри route handler и middleware не требуют дополнительных действий. Если синхронный код выбрасывает ошибку, Express сам ее перехватит и обработает. Например:
1 2 3 | |
Ошибки из асинхронных функций, вызываемых route handler и middleware, нужно передавать в next(), тогда Express их перехватит и обработает. Например:
1 2 3 4 5 6 7 8 9 | |
Начиная с Express 5, route handler и middleware, возвращающие Promise, автоматически вызывают next(value) при отклонении Promise или выбрасывании ошибки. Например:
1 2 3 4 | |
Если getUserById выбросит ошибку или вернет отклоненный Promise, next будет вызван с этой ошибкой или значением отклонения. Если значение отклонения отсутствует, next будет вызван со стандартным объектом Error от роутера Express.
Если передать в next() любое значение (кроме строки 'route'), Express считает текущий запрос ошибочным и пропускает оставшиеся route handler и middleware, не предназначенные для обработки ошибок.
Если callback в цепочке возвращает только ошибки (без данных), код можно упростить так:
1 2 3 4 5 6 7 8 | |
В примере выше next передается как callback в fs.writeFile, который вызывается и при ошибке, и без нее. Если ошибки нет, выполняется второй handler; иначе Express перехватывает и обрабатывает ошибку.
Ошибки, возникающие в асинхронном коде, вызванном из route handler или middleware, нужно перехватывать и передавать в Express на обработку. Например:
1 2 3 4 5 6 7 8 9 | |
В примере выше используется блок try...catch, чтобы перехватить ошибку в асинхронном коде и передать ее в Express. Без try...catch Express не поймает ошибку, потому что она возникает вне синхронного кода обработчика.
Используйте promises, чтобы избежать избыточного try...catch, либо при работе с функциями, возвращающими promises. Например:
1 2 3 4 5 6 7 | |
Так как promises автоматически перехватывают и синхронные ошибки, и отклоненные promises, можно просто передать next в финальный catch, и Express обработает ошибку, потому что catch получает ее первым аргументом.
Также можно использовать цепочку handler-ов и полагаться на синхронный перехват ошибок, сведя асинхронный код к минимуму. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
В примере выше после readFile выполняется несколько простых операций. Если readFile возвращает ошибку, она передается в Express; иначе управление быстро возвращается в синхронную часть следующего handler-а. Далее выполняется обработка данных. Если она падает, ошибку перехватит синхронный механизм. Если бы обработка выполнялась внутри callback readFile, приложение могло бы завершиться, и обработчики ошибок Express не сработали бы.
Какой бы метод вы ни выбрали, чтобы обработчики ошибок Express сработали, а приложение продолжило работу, нужно гарантировать, что ошибка попадает в Express.
Обработчик ошибок по умолчанию¶
В Express есть встроенный обработчик ошибок, который обрабатывает любые ошибки в приложении. Этот middleware добавляется в конец стека middleware-функций.
Если вы передали ошибку в next(), но не обработали ее в своем error handler, сработает встроенный обработчик: клиент получит ответ с текстом ошибки и stack trace. В production stack trace не включается.
Установите переменную окружения NODE_ENV в production, чтобы запустить приложение в production-режиме.
Когда формируется ответ об ошибке, в него добавляются:
res.statusCodeустанавливается изerr.status(илиerr.statusCode). Если значение вне диапазона 4xx/5xx, будет установлено 500.res.statusMessageустанавливается согласно коду статуса.- В production тело ответа — HTML-сообщение статуса, иначе —
err.stack. - Любые заголовки, указанные в объекте
err.headers.
Если вызвать next() с ошибкой после начала отправки ответа (например, при ошибке во время стриминга ответа клиенту), обработчик Express по умолчанию закроет соединение и завершит запрос с ошибкой.
Поэтому при добавлении собственного обработчика ошибок нужно делегировать в стандартный обработчик Express, если заголовки уже отправлены клиенту:
1 2 3 4 5 6 7 | |
Учтите, что стандартный обработчик может сработать, если в коде вы вызываете next() с ошибкой больше одного раза, даже при наличии собственного middleware обработки ошибок.
Другие middleware для обработки ошибок можно найти в каталоге Express middleware.
Написание обработчиков ошибок¶
Middleware обработки ошибок определяется так же, как и обычный middleware, но принимает четыре аргумента вместо трех: (err, req, res, next). Например:
1 2 3 4 | |
Middleware обработки ошибок объявляется в конце — после остальных вызовов app.use() и маршрутов. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Ответы из middleware могут быть в любом формате: HTML-страница ошибки, простое сообщение или JSON.
Для лучшей организации (и в более высокоуровневых фреймворках) можно определить несколько middleware обработки ошибок, как и обычных middleware. Например, отдельно для запросов через XHR и остальных:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
В этом примере общий logErrors может писать информацию о запросе и ошибке в stderr, например:
1 2 3 4 | |
Также в примере clientErrorHandler определен следующим образом; здесь ошибка явно передается дальше.
Обратите внимание: если в функции обработки ошибок вы не вызываете next, то обязаны сами сформировать и завершить ответ. Иначе такие запросы «повиснут» и не смогут быть корректно освобождены сборщиком мусора.
1 2 3 4 5 6 7 8 9 | |
Реализовать универсальный "catch-all" errorHandler можно так (пример):
1 2 3 4 | |
Если у route handler несколько callback-функций, можно использовать параметр route, чтобы перейти к следующему route handler. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
В этом примере handler getPaidContent будет пропущен, но оставшиеся handler-ы в app для /a_route_behind_paywall продолжат выполняться.
Вызовы next() и next(err) показывают, что текущий handler завершен и в каком состоянии. next(err) пропустит все оставшиеся handler-ы в цепочке, кроме тех, которые настроены на обработку ошибок, как описано выше.