错误处理是指 Express 如何捕获和处理同步和异步发生的错误。Express 带有一个默认的错误处理程序,因此您无需编写自己的错误处理程序即可开始使用。
确保 Express 捕获在运行路由处理程序和中间件时发生的错误非常重要。
在路由处理程序和中间件中的同步代码中发生的错误不需要额外的操作。如果同步代码抛出错误,Express 将捕获并处理它。例如
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express will catch this on its own.
})
对于由路由处理程序和中间件调用的异步函数返回的错误,您必须将它们传递给 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 开始,返回 Promise 的路由处理程序和中间件将在它们拒绝或抛出错误时自动调用 next(value)
。例如
app.get('/user/:id', async (req, res, next) => {
const user = await getUserById(req.params.id)
res.send(user)
})
如果 getUserById
抛出错误或拒绝,next
将使用抛出的错误或拒绝的值被调用。如果没有提供拒绝的值,next
将使用 Express 路由器提供的默认错误对象被调用。
如果您向 next()
函数传递任何内容(除了字符串 'route'
),Express 将认为当前请求是错误,并将跳过任何剩余的非错误处理路由和中间件函数。
如果序列中的回调不提供数据,只提供错误,您可以简化此代码,如下所示
app.get('/', [
function (req, res, next) {
fs.writeFile('/inaccessible-path', 'data', next)
},
function (req, res) {
res.send('OK')
}
])
在上面的例子中,next
被提供为 fs.writeFile
的回调函数,它会在有或没有错误的情况下被调用。如果没有错误,第二个处理程序将被执行,否则 Express 会捕获并处理错误。
您必须捕获由路由处理程序或中间件调用的异步代码中发生的错误,并将它们传递给 Express 进行处理。例如
app.get('/', (req, res, next) => {
setTimeout(() => {
try {
throw new Error('BROKEN')
} catch (err) {
next(err)
}
}, 100)
})
上面的例子使用 try...catch
块来捕获异步代码中的错误,并将它们传递给 Express。如果省略了 try...catch
块,Express 不会捕获错误,因为它不是同步处理程序代码的一部分。
使用 Promise 来避免 try...catch
块的开销,或者在使用返回 Promise 的函数时。例如
app.get('/', (req, res, next) => {
Promise.resolve().then(() => {
throw new Error('BROKEN')
}).catch(next) // Errors will be passed to Express.
})
由于 Promise 会自动捕获同步错误和被拒绝的 Promise,您只需提供 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 带有一个内置的错误处理程序,它负责处理应用程序中可能遇到的任何错误。这个默认的错误处理中间件函数被添加到中间件函数栈的末尾。
如果您将错误传递给 next()
,并且您没有在自定义错误处理程序中处理它,它将由内置的错误处理程序处理;错误将与堆栈跟踪一起写入客户端。堆栈跟踪不包含在生产环境中。
将环境变量 NODE_ENV
设置为 production
,以在生产模式下运行应用程序。
当发生错误时,以下信息将添加到响应中
res.statusCode
从 err.status
(或 err.statusCode
)设置。如果此值不在 4xx 或 5xx 范围内,它将被设置为 500。res.statusMessage
根据状态码设置。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()
,即使存在自定义错误处理中间件,默认错误处理程序也会被触发。
其他错误处理中间件可以在 Express 中间件 中找到。
以与其他中间件函数相同的方式定义错误处理中间件函数,除了错误处理函数有四个参数而不是三个:(err, req, res, next)
。例如
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
您在其他 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
})
中间件函数中的响应可以是任何格式,例如 HTML 错误页面、简单消息或 JSON 字符串。
出于组织(和更高级别的框架)目的,您可以定义多个错误处理中间件函数,就像您使用常规中间件函数一样。例如,要为使用 XHR
和不使用 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)
将跳过链中的所有剩余处理程序,除了那些设置为处理错误的处理程序(如上所述)。