前言
今天想写一下eggjs的自定义异常处理中间件,在写的时候遇到了问题,这个错误我捕获不到类型??
处理过程,不喜欢看过程的朋友请直接看解决方法和总结
看一下是什么:
抛出的异常是检验失败异常Validation Failed (code: invalid_param)
我写了个异常处理中间件,用来处理业务中的异常
module.exports = (options, app) => {
return async function testMiddleware(ctx, next) {
try{
await next();
}
catch (err) {
// 记录到日志
ctx.logger.error(err);
ctx.throw(err);
}
};
};
具体思路是想要根据异常的类型来实现自定义的处理,如验证失败就不走onerror。如果不是想要处理的错误就ctx.throw(),丢给onerror继续处理。
看了下抛出的错误类型是:UnprocessableEntityError
也没多想,就用instanceof判断一下吧:
module.exports = (options, app) => {
return async function testMiddleware(ctx, next) {
try{
await next();
}
catch (err) {
if (err instanceof UnprocessableEntityError) {
// 如果是这个错误那么就自己处理,否则就抛出
// 记录到日志
ctx.logger.error(err);
ctx.body = {
code: err.status,
err
}
} else {
ctx.throw(err);
}
}
};
};
然后就报了‘UnprocessableEntityError’ undefine错误。。
对的,UnprocessableEntityError我没有引入,nodejs自带也没这个错误?那这个错误哪来的?
接着我顺着错误去找抛出的源头,找到了这个:
this.throw是koa 的throw方法:
在node_modules_koa@2.7.0@koalibcontext.js的throw函数抛出了这个错误,
具体使用的是createError这个函数,再去代码目录下找createError:
webstorm ctrl+鼠标左键定位跳转一下,发现原来使用的是http-errors模块里面的里面的createError方法,下面是具体的函数内容:
// node_modules\_http-errors@1.7.3@http-errorsindex.js
/**
* Create a new HTTP Error.
*
* @returns {Error}
* @public
*/
function createError () {
// so much arity going on ~_~
var err
var msg
var status = 500
var props = {}
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i]
if (arg instanceof Error) { // 如果参数类型就是Error那么err就是arg
err = arg
status = err.status || err.statusCode || status
continue // 跳过以下代码,进入下一轮循环
}
switch (typeof arg) {
case 'string': // 如果arg是string 那么err的msg就是arg
msg = arg
break
case 'number': // 如果是number 那么错误的status就是arg,这里要求statu是第一个参数
status = arg
if (i !== 0) {
deprecate('non-first-argument status code; replace with createError(' + arg + ', ...)')
}
break
case 'object': // 如果是类,那么把类挂载到err上
props = arg
break
}
}
// 循环完毕了,来判断一下状态码
if (typeof status === 'number' && (status < 400 || status >= 600)) {
deprecate('non-error status code; use only 4xx or 5xx status codes')
}
if (typeof status !== 'number' ||
(!statuses[status] && (status < 400 || status >= 600))) {
status = 500
}
// constructor
var HttpError = createError[status] || createError[codeClass(status)]
// 我们传的是422,所以返回了UnprocessableEntityError这个构造器
if (!err) { // 如果不是直接传入Error
// create error
err = HttpError // 如果HttpError为空,HttpError里面没有这个错误
? new HttpError(msg) // 执行到这一步,返回一个新的错误,
: new Error(msg || statuses[status])
Error.captureStackTrace(err, createError) // 传入堆栈信息
}
if (!HttpError || !(err instanceof HttpError) || err.status !== status) { //不执行
// add properties to generic error
err.expose = status < 500
err.status = err.statusCode = status
}
for (var key in props) {
if (key !== 'status' && key !== 'statusCode') { //将数据挂载上去
err[key] = props[key]
}
}
return err
}
大概搞明白了
就是validate在抛出异常之后,调用了koa的throw又调用了http-errors里面的createError,createError先去找有咩有这个http错误,常见的什么404啊403啊500啊之类的,没找到就直接把用Error构造,找到了就用对应的错误类型构造,这些类型构造又是存在:node_modules/koa/node_modules/http-errors/node_modules/statuses/index.js
里面的,比如上面的422
对应的就是UnprocessableEntityError这个异常。
解决方法
到了这里,解决就变的很轻松了,我们要捕获的异常是UnprocessableEntityError,那么可以使用err.name来获取错误的名字,获取到了之后,再根据
validate抛出的固定错误码'Validation Failed'来确定错误。
实际上也可以直接根据这个错误码来判断,这个错误码经历了HttpError的洗礼之后变成了message,可以通过err.message来获取。
那么代码如下:
module.exports = (options, app) => {
return async function testMiddleware(ctx, next) {
try{
await next();
}
catch (err) {
if (err.message === 'Validation Failed') {
// 记录到日志
ctx.logger.error(err);
ctx.body = {
code: err.status,
err
}
} else {
ctx.throw(err);
}
}
};
};
再次打开浏览器尝试一下,这个异常被正确的返回给前端了:
总结一下
- 错误类型,既错误名可以使用err.name获取
- 对应的错误消息(相比HttpError更精细的)使用err.message获取
- 错误哪怕被自己处理了也还是写入日志,logger.error()一下比较好
- 你不想处理的错误还是交给onerror来办,使用ctx.throw丢给它
- 记得处理错误要返回信息,既设置ctx.body,不然会返回404的