Обработка синхронных ошибок
Если вам нужно обработать синхронную ошибку, то вы можете, для начала, с помощью инструкции throw, выдать такую ошибку в обработчике запроса Express. Обратите внимание на то, что обработчики запросов ещё называют «контроллерами», но я предпочитаю использовать термин «обработчик запросов» так как он кажется мне понятнее.
Вот как это выглядит:
app.post('/testing', (req, res) => { throw new Error('Something broke! ') })
Такие ошибки можно перехватить с помощью обработчика ошибок Express. Если вы не написали собственный обработчик ошибок (подробнее об этом мы поговорим ниже), то Express обработает ошибку с помощью обработчика, используемого по умолчанию.
Вот что делает стандартный обработчик ошибок Express:
- Устанавливает код состояния HTTP-ответа в значение 500.
- Отправляет сущности, выполнившей запрос, текстовый ответ.
- Логирует текстовый ответ в консоль.

Сообщение об ошибке, выведенное в консоль
Обработка асинхронных ошибок
Для обработки асинхронных ошибок нужно отправить ошибку обработчику ошибок Express через аргумент next:
app.post('/testing', async (req, res, next) => { return next(new Error('Something broke again! ')) })
Вот что попадёт в консоль при логировании этой ошибки.

Сообщение об ошибке, выведенное в консоль
Если вы пользуетесь в Express-приложении конструкцией async/await, то вам понадобится использовать функцию-обёртку, наподобие express-async-handler. Это позволяет писать асинхронный код без блоков try/catch. Подробнее об async/await в Express можно почитать здесь.
const asyncHandler = require('express-async-handler') app.post('/testing', asyncHandler(async (req, res, next) => { // Сделать что-нибудь }))
После того, как обработчик запроса обёрнут в express-async-handler, то можно, так же, как было описано выше, выбросить ошибку с использованием инструкции throw. Эта ошибка попадёт к обработчику ошибок Express.
app.post('/testing', asyncHandler(async (req, res, next) => { throw new Error('Something broke yet again! ') }))

Сообщение об ошибке, выведенное в консоль
Написание собственного обработчика ошибок
Обработчики ошибок Express принимают 4 аргумента:
- error
- req
- res
- next
Размещать их нужно после промежуточных обработчиков и маршрутов.
app.use(/*...*/) app.get(/*...*/) app.post(/*...*/) app.put(/*...*/) app.delete(/*...*/) // Собственный обработчик ошибок нужно поместить после всех остальных промежуточных обработчиков app.use((error, req, res, next) => { /* ... */ })
Если создать собственный обработчик ошибок, то Express прекратит использование стандартного обработчика. Для того чтобы обработать ошибку, нужно сформировать ответ для фронтенд-приложения, которое обратилось к конечной точке, в которой возникла ошибка. Это означает, что нужно выполнить следующие действия:
- Сформировать и отправить подходящий код состояния ответа.
- Сформировать и отправить подходящий ответ.
То, какой именно код состояния подойдёт в каждом конкретном случае, зависит от того, что именно произошло. Вот список типичных ошибок, к обработке которых вы должны быть готовы:
- Ошибка
400 Bad Request. Используется в двух ситуациях. Во-первых — тогда, когда пользователь не включил в запрос необходимое поле (например — в отправленной платёжной форме не заполнено поле со сведениями о кредитной карте). Во-вторых — тогда, когда в запросе содержатся некорректные данные (например — ввод в поле пароля и в поле подтверждения пароля разных паролей). - Ошибка
401 Unauthorized. Этот код состояния ответа применяется в том случае, если пользователь ввёл неправильные учётные данные (вроде имени пользователя, адреса электронной почты или пароля). - Ошибка
403 Forbidden. Используется в тех случаях, когда пользователю не разрешён доступ к конечной точке. - Ошибка
404 Not Found. Применяется в тех случаях, когда конечную точку невозможно обнаружить. - Ошибка
500 Internal Server Error. Применяется тогда, когда запрос, отправленный фронтендом, сформирован правильно, но на бэкенде при этом возникла какая-то ошибка.
После того, как определён подходящий код состояния ответа, его нужно установить с помощью res.status:
app.use((error, req, res, next) => { // Ошибка, выдаваемая в ответ на неправильно сформированный запрос res.status(400) res.json(/* ... */) })
Код состояния ответа должен соответствовать сообщению об ошибке. Для этого нужно отправлять код состояния вместе с ошибкой.
Легче всего это сделать с помощью пакета http-errors. Он позволяет отправлять в ошибке три фрагмента информации:
- Код состояния ответа.
- Сообщение, сопутствующее ошибке.
- Любые данные, которые нужно отправить (это необязательно).
Вот как установить пакет http-errors:
npm install http-errors --save
Вот как этим пакетом пользоваться:
const createError = require('http-errors') // Создание ошибки throw createError(status, message, properties)
Рассмотрим пример, который позволит как следует в этом всём разобраться.
Представим, что мы пытаемся обнаружить пользователя по адресу его электронной почты. Но этого пользователя найти не удаётся. В результате мы решаем отправить в ответ на соответствующий запрос ошибку User not found, сообщающую вызывающей стороне о том, что пользователь не найден.
Вот что нам нужно будет сделать при создании ошибки:
- Установить код состояния ответа как
400 Bad Request(ведь пользователь ввёл неправильные данные). Это будет наш первый параметр. - Отправить вызывающей стороне сообщение наподобие
User not found. Это будет второй параметр.
app.put('/testing', asyncHandler(async (req, res) => { const { email } = req.body const user = await User.findOne({ email }) // Если пользователь не найден - выбросим ошибку if (!user) throw createError(400, `User '${email}' not found`) }))
Получить код состояния можно с помощью конструкции error.status, а сообщение ошибки — с помощью error.message:
// Логирование ошибки app.use((error, req, res, next) => { console.log('Error status: ', error.status) console.log('Message: ', error.message) })

Результат логирования ошибки в консоли
Затем состояние ответа устанавливают с помощью res.status, а сообщение записывают в res.json:
app.use((error, req, res, next) => { // Установка кода состояния ответа res.status(error.status) // Отправка ответа res.json({ message: error.message }) })
Лично я предпочитаю отправлять в подобных ответах код состояния, сообщение и результат трассировки стека. Это облегчает отладку.
app.use((error, req, res, next) => { // Установка кода состояния ответа res.status(error.status) // Отправка ответа res.json({ status: error.status, message: error.message, stack: error.stack }) })
▍Код состояния ответа, используемый по умолчанию
Если источником ошибки не является createError, то у неё не будет свойства status. Вот пример, в котором сделана попытка прочесть несуществующий файл с помощью fs.readFile:
const fs = require('fs') const util = require('util') // Преобразуем readFile из функции, использующей коллбэки, в async/await-функцию. // Подробности об этом смотрите здесь: https://zellwk.com/blog/callbacks-to-promises const readFilePromise = util.promisify(fs.readFile) app.get('/testing', asyncHandler(async (req, res, next) => { const data = await readFilePromise('some-file') })
У такого объекта ошибки не будет свойства status:
app.use((error, req, res, next) => { console.log('Error status: ', error.status) console.log('Message: ', error.message) })

Результат логирования ошибки в консоли
В подобных случаях можно задать код ошибки, используемый по умолчанию. А именно, речь идёт об ошибке 500 Internal Server Error:
app.use((error, req, res, next) => { res.status(error.status || 500) res.json({ status: error.status, message: error.message, stack: error.stack }) })
▍Изменение кода состояния ошибки
Предположим, мы собираемся прочитать некий файл, воспользовавшись данными, предоставленными пользователем. Если такого файла не существует, это значит, что нам нужно выдать ошибку 400 Bad Request. Ведь в том, что файл найти не удаётся, нет вины сервера.
В подобном случае нужно воспользоваться конструкцией try/catch для перехвата исходной ошибки. Затем нужно воссоздать объект ошибки с помощью createError:
app.get('/testing', asyncHandler(async (req, res, next) => { try { const { file } = req.body const contents = await readFilePromise(path.join(__dirname, file)) } catch (error) { throw createError(400, `File ${file} does not exist`) } })
▍Обработка ошибок 404
Если запрос прошёл через все промежуточные обработчики и маршруты, но так и не был обработан, это означает, что конечная точка, соответствующая такому запросу, не была найдена.
Для обработки ошибок 404 Not Found нужно добавить, между маршрутами и обработчиком ошибок, дополнительный обработчик. Вот как выглядит создание объекта ошибки 404:
// Промежуточные обработчики... // Маршруты... app.use((req, res, next) => { next(createError(404)) }) // Обработчик ошибок...

Сведения об ошибке
▍Замечания об ошибке ERR_HTTP_HEADERS_SENT
Не впадайте в панику если видите сообщение об ошибке ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client. Она возникает из-за того, что в одном и том же обработчике многократно вызывается метод, устанавливающий заголовки ответа. Вот методы, вызов которых приводит к автоматической установке заголовков ответа:
- res.send
- res.json
- res.render
- res.sendFile
- res.sendStatus
- res.end
- res.redirect
Так, например, если вы вызовете методы res.render и res.json в одном и том же обработчике ответа, то вы получите ошибку ERR_HTTP_HEADERS_SENT:
app.get('/testing', (req, res) => { res.render('new-page') res.json({ message: '¯\_(ツ)_/¯' }) })
В результате, в том случае, если вы сталкиваетесь с этой ошибкой, тщательно проверьте код обработчиков ответа и убедитесь в том, что в нём нет ситуаций, в которых вызывается несколько вышеописанных методов.
▍Обработка ошибок и потоковая передача данных
Если что-то идёт не так при потоковой передаче ответа фронтенду, то можно столкнуться с той же самой ошибкой ERR_HTTP_HEADERS_SENT.
В подобном случае обработку ошибок нужно передать стандартным обработчикам. Такой обработчик отправит ошибку и автоматически закроет соединение.
app.use((error, req, res, next) => { // Сделать это нужно только в том случае, если ответ передаётся в потоковом режиме if (res.headersSent) { return next(error) } // Остальной код обработки ошибок })
Итоги
Сегодня я рассказал вам всё, что знаю об обработке ошибок в Express. Надеюсь, это поможет вам писать более надёжные Express-приложения.
Уважаемые читатели! Как вы обрабатываете ошибки в своих Node.js-проектах?

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/476290/


Добавить комментарий