Express框架中请求内的错误处理

Whichever method you use, if you want Express error handlers to be called in and the application to survive, you must ensure that Express receives the error.

Express官方文档中提到,要确保你的Express程序在遇到错误的时候不会宕机,就得确保Express能接收到程序抛出的错误。

uncaughtException导致的node宕机

如上所说,Express程序必须手动处理所有可能抛出的错误才不会发生宕机,或者说降低宕机的风险,这和node是息息相关的,毕竟Express是基于Nodejs的,在node中,在遇到uncaughtException时会直接结束node进程,之前写过一篇文章

Promise的UnhandledPromiseRejectionWarning问题

就是提到的node的这个特性,对Promise对象没有用.catch()来捕捉错误就会使node可能宕机。

当然了我们可以用pm2或者forever之类的node进程管理工具来管理重启和记录错误,这样也更利于我们之后对程序进行修复。

但是,在运行过程中,我们还是更希望我们的程序宕机的次数越少越好,如果我们的程序的Session是存储在内存中的,那么程序宕机重启之后,所有的用户登录状态也将不复存在。

Error Handler 错误处理函数

Express框架内置了一个错误处理函数(Error Handler),这个函数是以中间件(MiddleWare)的形式来使用的,只不过这个中间件和其他的中间件稍微不一样,那就是错误处理函数接受4个参数,比常规中间件多了接收错误的参数err

1
2
3
app.use(function(err, req, res, next) {
// 处理错误
});

错误处理函数一般定义在所有路由之后,为了确保Express程序不会因为错误而宕机,则需要把错误抛给这个错误处理函数来处理,在请求内有两种方式把错误抛给错误处理函数。

直接throw new Error()

1
2
3
app.get('/', function (req, res) {
throw new Error('BROKEN') // Express will catch this on its own.
})

或者在next()函数中传入错误对象

1
2
3
4
5
6
7
8
9
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)
}
})
})

直接抛出错误,或者在next()函数中传入错误对象都能直接跳过剩余的路由,直接进入错误处理函数。

参考官方文档

If you pass anything to the next() function (except the string 'route'), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.

next()函数

上面的官方文档说道 except the string 'route'

意思即是next()直接跳到下一个中间件;传的值是字符串routenext('route')时,直接跳到下一个路由;传入其他值都会跳到错误处理函数。

  • next()——跳到下一个中间件
  • next(‘route’)——跳到下一个路由
  • next(err)——跳到错误处理函数

注意next()next('route')是分别跳到下一个中间件路由,经常看到说next()函数会跳到下一个路由的,其实这样说是不准确的,一个路由可以有多个中间件来处理

1
2
3
4
5
6
7
mw1 = function(req, res, next){}
mw2 = function(req, res, next){}
mw3 = function(req, res, next){}
mw4 = function(req, res, next){}

app.get('/', mw1, mw2, mw3)
app.get('/', mw4)

例如在mw1函数中调用了next(),实际上是跳到mw2开始执行,而如果我们调用的是next('route'),那么就会直接跳过mw2mw3,直接开始执行mw4

之所以有时候混淆是因为经常一个路由我们只用一个函数来处理,而不是链式处理,一个路由一个函数处理,调用next()和调用next('route')的效果是一样的。

值得一提的还有,在官方文档中还提到

Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error.

也就是说,从Express5开始,在请求中或者中间件中的Promise对象,即使没有手动调用next(err)throw new Error(),如果Promise中抛出了错误或者reject了,那么Express会自动调用next(err),其中的err为Promise内抛出的错误或者reject的值,如果不存在就传入一个默认的Error对象。

本文由adiynil原创编写,转载请尽量保留版权网址,感谢您的理解与分享!