Обработка ошибок
Обработка ошибок описывает, как Express ловит и обрабатывает ошибки, возникающие как синхронно, так и асинхронно. Express поставляется со встроенным обработчиком ошибок, поэтому вам не нужно писать свой собственный, чтобы начать работу.
Перехват ошибок
Важно гарантировать, что Express ловит все ошибки, возникающие во время выполнения обработчиков маршрутов и middleware.
Ошибки, возникающие в синхронном коде внутри обработчиков маршрутов и middleware, не требуют дополнительных действий. Если синхронный код генерирует ошибку, Express перехватит и обработает её. Например:
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express will catch this on its own.
})
Для ошибок, возвращаемых асинхронными функциями, вызываемыми обработчиками маршрутов и middleware, необходимо передать их в функцию next(), где Express перехватит и обработает их. Например:
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
Начиная с Express 5, обработчики маршрутов и middleware, возвращающие Promise, автоматически вызовут next(value) при отклонении или генерации ошибки. Например:
app.get('/user/:id', async (req, res, next) => {
const user = await getUserById(req.params.id)
res.send(user)
})
Если getUserById генерирует ошибку или отклоняется, next будет вызван с ошибкой или отклоненным значением. Если отклоненное значение не указано, next будет вызван с объектом Error, предоставляемым маршрутизатором Express по умолчанию.
Если вы передадите что-либо функции next() (кроме строки 'route'), Express расценит текущий запрос как ошибку и пропустит все последующие функции маршрутизации и middleware, не относящиеся к обработке ошибок.
Если обратный вызов в последовательности не предоставляет данных, а только ошибки, вы можете упростить этот код следующим образом:
app.get('/', [
function (req, res, next) {
fs.writeFile('/inaccessible-path', 'data', next)
},
function (req, res) {
res.send('OK')
}
])
В приведённом выше примере next указан как обратный вызов для fs.writeFile, который вызывается с ошибками или без них. Если ошибки нет, выполняется второй обработчик, в противном случае Express перехватывает и обрабатывает ошибку.
Необходимо перехватывать ошибки, возникающие в асинхронном коде, вызываемом обработчиками маршрутов или middleware, и передавать их Express для обработки. Например:
app.get('/', (req, res, next) => {
setTimeout(() => {
try {
throw new Error('BROKEN')
} catch (err) {
next(err)
}
}, 100)
})
В приведённом выше примере используется блок try...catch, чтобы перехватить ошибки в асинхронном коде и передать их Express. Если блок try...catch был опущен, Express не перехватил бы ошибку, поскольку он не является частью синхронного кода обработчика.
Используйте promises, чтобы избежать накладных расходов блока try...catch или при работе с функциями, возвращающими promises. Например:
app.get('/', (req, res, next) => {
Promise.resolve().then(() => {
throw new Error('BROKEN')
}).catch(next) // Errors will be passed to Express.
})
Поскольку promises автоматически перехватывают как синхронные ошибки, так и отклоненные promises, вы можете просто указать next в качестве конечного обработчика catch, и Express перехватит ошибки, потому что обработчику catch передаётся ошибка в качестве первого аргумента.
Вы также можете использовать цепочку обработчиков, чтобы полагаться на синхронный перехват ошибок, сводя асинхронный код к чему-то тривиальному. Например:
app.get('/', [
function (req, res, next) {
fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
res.locals.data = data
next(err)
})
},
function (req, res) {
res.locals.data = res.locals.data.split(',')[1]
res.send(res.locals.data)
}
])
В приведённом выше примере есть несколько тривиальных утверждений из вызова readFile. Если readFile вызывает ошибку, то передаёт ошибку Express, в противном случае быстро возвращается к миру синхронной обработки ошибок в следующем обработчике в цепочке. Затем пример пытается обработать данные. Если это не удаётся, то синхронный обработчик ошибок перехватит её. Если бы вы выполнили эту обработку внутри обратного вызова readFile, то приложение, возможно, завершилось бы, и обработчики ошибок Express не сработали бы.
Какой бы метод вы ни использовали, если вы хотите, чтобы обработчики ошибок Express вызывались, и приложение работало, вы должны убедиться, что Express получает ошибку.
Обработчик ошибок по умолчанию
Express поставляется со встроенным обработчиком ошибок, который обрабатывает любые ошибки, которые могут возникнуть в приложении. Эта функция middleware по умолчанию добавлена в конец стека функций middleware.
Если вы передадите ошибку в next() и не обработаете её в пользовательском обработчике ошибок, она будет обработана встроенным обработчиком; ошибка будет выведена клиенту со стеком вызовов. Стек вызовов не включается в среде производства.
Установите переменную среды NODE_ENV в значение production, чтобы запустить приложение в режиме производства.
При записи ошибки в ответ добавляется следующая информация:
- Код состояния устанавливается из
err.status(илиerr.statusCode). Если это значение находится вне диапазона 4xx или 5xx, оно будет установлено в 500. - Сообщение состояния устанавливается в соответствии с кодом состояния.
- Тело будет содержать HTML-сообщение об коде состояния при запуске в среде производства, в противном случае будет
err.stack. - Любые заголовки, указанные в объекте
err.headers.
Если вы вызовете next() с ошибкой после начала записи ответа (например, если вы столкнулись с ошибкой при потоковой передаче ответа клиенту), обработчик ошибок Express по умолчанию закрывает соединение и завершает запрос.
Поэтому, когда вы добавляете пользовательский обработчик ошибок, вы должны делегировать обработку ошибок в обработчик ошибок Express по умолчанию, когда заголовки уже отправлены клиенту:
function errorHandler (err, req, res, next) {
if (res.headersSent) {
return next(err)
}
res.status(500)
res.render('error', { error: err })
}
Обратите внимание, что обработчик ошибок по умолчанию может быть вызван, если вы вызовете next() с ошибкой в вашем коде более одного раза, даже если пользовательские обработчики ошибок middleware присутствуют.
Написание обработчиков ошибок
Определите функции middleware для обработки ошибок таким же образом, как и другие функции middleware, за исключением того, что функции обработки ошибок имеют четыре аргумента вместо трёх: (err, req, res, next). Например:
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
Вы определяете middleware для обработки ошибок в последнюю очередь, после вызовов других app.use() и маршрутов; например:
const bodyParser = require('body-parser')
const methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
// logic
})
Ответы внутри функции middleware могут быть в любом формате, например, HTML-страница с ошибкой, простое сообщение или строка JSON.
Для организационных целей (и фреймворка более высокого уровня) вы можете определить несколько функций middleware для обработки ошибок так же, как и обычные функции middleware. Например, чтобы определить обработчик ошибок для запросов, сделанных с использованием XHR и без них:
const bodyParser = require('body-parser')
const methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)
В этом примере общий обработчик logErrors может записывать информацию о запросе и ошибке в stderr, например:
function logErrors (err, req, res, next) {
console.error(err.stack)
next(err)
}
Также в этом примере clientErrorHandler определён следующим образом; в этом случае ошибка явно передаётся дальше.
Обратите внимание, что, не вызывая «next» в функции обработки ошибок, вы несете ответственность за запись (и завершение) ответа. В противном случае эти запросы «зависнут» и не будут подходить для сборки мусора.
function clientErrorHandler (err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something failed!' })
} else {
next(err)
}
}
Реализуйте функцию «catch-all» errorHandler следующим образом (например):
function errorHandler (err, req, res, next) {
res.status(500)
res.render('error', { error: err })
}
Если у вас есть обработчик маршрута с несколькими функциями обратного вызова, вы можете использовать параметр route для перехода к следующему обработчику маршрута. Например:
app.get('/a_route_behind_paywall',
(req, res, next) => {
if (!req.user.hasPaid) {
// continue handling this request
next('route')
} else {
next()
}
}, (req, res, next) => {
PaidContent.find((err, doc) => {
if (err) return next(err)
res.json(doc)
})
})
В этом примере обработчик getPaidContent будет пропущен, но все оставшиеся обработчики в app для /a_route_behind_paywall будут продолжать выполняться.
Вызовы next() и next(err) указывают, что текущий обработчик завершён и в каком состоянии. next(err) пропустит все оставшиеся обработчики в цепочке, кроме тех, которые настроены на обработку ошибок, как описано выше.
© 2017 StrongLoop, IBM, and other expressjs.com contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v3.0.
https://expressjs.com/en/guide/error-handling.html