前言
今天想寫一下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@koa\lib\context.js的throw函數拋出了這個錯誤,
具體使用的是createError這個函數,再去代碼目錄下找createError:
webstorm ctrl+鼠標左鍵定位跳轉一下,發現原來使用的是http-errors模塊里面的里面的createError方法,下面是具體的函數內容:
// node_modules\_http-errors@1.7.3@http-errors\index.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的
