错误处理是指Express如何捕获和处理同步和异步发生的错误。Express带有默认的错误处理程序,因此您无需自己编写即可开始使用。
捕捉错误
重要的是要确保Express捕获运行路由处理程序和中间件时发生的所有错误。
路由处理程序和中间件内部的同步代码中发生的错误不需要任何额外的工作。如果同步代码引发错误,则Express将捕获并处理该错误。例如:
app.get('/', function (req, res) {
throw new Error('BROKEN') // Express will catch this on its own.
})
对于从路由处理程序和中间件调用的异步函数返回的错误,必须将它们传递给next()
Express捕获并处理它们的函数。例如:
app.get('/', function (req, res, next) {
fs.readFile('/file-does-not-exist', function (err, data) {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
从Express 5开始,返回Promise的路由处理程序和中间件next(value)
在拒绝或引发错误时将自动调用。例如:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
如果getUserById
抛出错误或拒绝,next
将使用抛出的错误或拒绝的值来调用。如果未提供拒绝值,next
将使用Express路由器提供的默认Error对象调用该值。
如果将任何内容传递给该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('/', function (req, res, next) {
setTimeout(function () {
try {
throw new Error('BROKEN')
} catch (err) {
next(err)
}
}, 100)
})
上面的示例使用一个try...catch
块来捕获异步代码中的错误,并将它们传递给Express。如果try...catch
省略了该块,则Express将不会捕获该错误,因为它不是同步处理程序代码的一部分。
使用promise可以避免try..catch
块的开销,或者在使用返回promise的函数时。例如:
app.get('/', function (req, res, next) {
Promise.resolve().then(function () {
throw new Error('BROKEN')
}).catch(next) // Errors will be passed to Express.
})
由于promise会自动捕获同步错误和已拒绝的promise,因此您可以简单地提供next
作为最终捕获处理程序,而Express将捕获错误,因为捕获处理程序将错误作为第一个参数。
您还可以使用一系列处理程序来依靠同步错误捕获,方法是将异步代码减少到微不足道的程度。例如:
app.get('/', [
function (req, res, next) {
fs.readFile('/maybe-valid-file', 'utf-8', function (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
根据状态代码集。 - 在生产环境中,正文为状态代码消息的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()
,即使使用自定义错误处理中间件,如果多次在代码中调用错误,也会触发默认错误处理程序。
编写错误处理程序
定义错误处理中间件功能的方式与其他中间件功能相同,只是错误处理功能具有四个参数而不是三个参数 (err, req, res, next)
。例如:
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
您最后定义错误处理中间件,之后再定义app.use()
并路由调用;例如:
var bodyParser = require('body-parser')
var methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(function (err, req, res, next) {
// 逻辑
})
来自中间件功能的响应可以是任何格式,例如HTML错误页面,简单消息或JSON字符串。
出于组织(和更高级别的框架)的目的,您可以定义多个错误处理中间件功能,就像使用常规中间件功能一样。例如,为使用XHR
和不使用的请求定义错误处理程序:
var bodyParser = require('body-parser')
var 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)
}
}
如下实现“包罗万象” errorHandler
功能:
function errorHandler (err, req, res, next) {
res.status(500)
res.render('error', { error: err })
}
如果您的路由处理程序具有多个回调函数,则可以使用route
参数跳到下一个路由处理程序。例如:
app.get('/a_route_behind_paywall',
function checkIfPaidSubscriber (req, res, next) {
if (!req.user.hasPaid) {
// continue handling this request
next('route')
} else {
next()
}
}, function getPaidContent (req, res, next) {
PaidContent.find(function (err, doc) {
if (err) return next(err)
res.json(doc)
})
})
在此示例中,getPaidContent
将跳过处理程序,但是app
for中剩余的所有处理程序/a_route_behind_paywall
将继续执行。
调用
next()
并next(err)
指示当前处理程序已完成并且处于什么状态。next(err)
将跳过链中所有剩余的处理程序,但如上所述的那些设置为处理错误的处理程序除外。