咱們在講解如何處理異常以前,須要先對Koa中間件機制進行了解。node
koa 的請求處理是典型的洋蔥模型,下面是官方的配圖,而這一模型的組成部分就是 middlewareapp
koa 中間件的執行機制,就是一個洋蔥模型,每次請求進來,先執行前面的中間件,遇到 next,執行下一個中間件,以此重複,執行完全部中間件後,再從最後一箇中間件往前執行koa
例子:異步
app.use(async (ctx, next) => { console.log(1) await next() console.log(2) }) app.use(async (ctx, next) => { console.log(3) await next() console.log(4) }) app.use(async (ctx, next) => { console.log(5) await next() console.log(6) }) app.use(async (ctx, next) => { console.log(7) }) // 輸出結果是 1 3 5 7 6 4 2
思考 - 中間件在處理異常的過程當中扮演了什麼角色?
常見拋出異常和錯誤類型async
提及異常捕獲,咱們最早想到的確定是 try catch, 其在node下是如何實現?例子:學習
const func = async (ctx, next) => { try { await next() } catch () { ctx.body = { //返回異常 } } } app.use(func) app.use(func1) app.use(func2)
可是try catch 有個問題,它沒法處理異步代碼塊內出現的異常。能夠理解爲在執行catch時,異常還沒發生ui
try { asyncError() } catch (e) { /*異常沒法被捕獲,致使進程退出*/ console.log(e.message) }
fs.mkdir('/dir', function (e) { if (e) { /*處理異常*/ console.log(e.message) } else { console.log('建立目錄成功') } })
new Promise((resolve, reject) => { syncError() }).then(() => { //... }).catch((e) => { /*處理異常*/ console.log(e.message) })
Promise一樣沒法處理異步代碼塊中拋出的異常this
Koa提供了ctx.throw(400)的方式,讓咱們便捷的拋出http錯誤,可是咱們在拋出http錯誤的同時想返回額外的信息?該如何實現?url
ctx.throw(400, 'name required', { user: user });
若須要定義若干業務邏輯錯誤碼和說明,返回不一樣的code,在controller層面,你也許能夠這樣處理:spa
router.get('/', (ctx, next) => { if (checkToken(token)) { const code = ERROR_CODE.TOKEN_ERROR ctx.body = { code, msg: ERROR_MSG[code] } return } // do something })
若是是在model層或者server層,要處理這樣的錯誤怎麼辦?
經過定義返回值來講明錯誤,在controller中判斷返回值再返回相應錯誤碼,好比:
const somefunc = async (token) => { const res = await tokenExpire(token) if (res) { return false } // do something }
拋出Error,在controller中catch住異常,並對比err.message來返回相應錯誤碼,好比:
const somefunc = async (token) => { const res = await tokenExpire(token) if (res) { throw Error(ERROR_MSG.TOKEN_ERROR) } // do something }
問題來了。
若是錯誤的類型,文言有不少種怎麼辦?
每次都須要進行if判斷,煩不煩?
process方式能夠捕獲任何異常(無論是同步代碼塊中的異常仍是異步代碼塊中的異常)
process.on('uncaughtException', function (e) { /*處理異常*/ console.log(e.message) }); asyncError() syncError()
const Koa = require('koa') const app = new Koa() app.on('error', (err, next) => { console.error('server error',err) }) const main = ctx => { ctx.throw(500) } app.use(main) app.listen(3000)
const Koa = require('koa') const app = new Koa() app.use( async (ctx, next) =>{ await next().catch(error => { console.log(error) }); }) const main = ctx => { ctx.throw(500) } app.use(main) app.listen(3000)
首先咱們使用一個一個斷言庫!
爲何要使用?參考throw方法中,咱們一般須要針對不一樣的業務邏輯場景進行返回錯誤。
好比: '用戶名不爲空','密碼不能爲空','起始日大於截止日','密碼輸入錯誤','用戶名不存在'...等等
若是採用throw方法,咱們須要定義不少code,msg來進行維護,好比:
ERROR_CODE:
ERROR_CODE = { SOME_CUSTOM_ERROR: 1001, EMPTY_PASSWORD: 1002 }
ERROR_MSG:
ERROR_MSG = { 1001: 'some custom error msg', 1002: '密碼不能爲空' }
可是使用斷言庫以後,咱們不須要去寫以下代碼,不須要額外維護code,msg
if (!ctx.request.body.password) { throw(...) } if (!ctx.request.body.name) { throw(...) }
只須要
assert.ok(data.password, 'password不能爲空') assert.ok(data.name, '用戶名不能爲空')
便可,返回以下
{ "code": 500, "msg": "password不能爲空", "data": {}, "success": false }
利用koa中間件加上咱們自定義的繼承於Error構造器的方法即可以實現。
function CustomError (code, msg) { Error.call(this, '') this.code = code this.msg = msg || ERROR_MSG[code] || 'unknown error' this.getCodeMsg = function () { return { code: this.code, msg: this.msg } } } util.inherits(CustomError, Error) function HttpError (code, msg) { if (Object.values(HTTP_CODE).indexOf(code) < 0) { throw Error('not an invalid http code') } CustomError.call(this, code, msg) } util.inherits(HttpError, CustomError)
router.get('/HttpError', (ctx, next) => { throw new HttpError(HTTP_CODE.FORBIDDEN) }) const somefunc = async (token) => { const res = await tokenExpire(token) if (res) { throw new CustomError(CUSTOM_CODE.SOME_CUSTOM_ERROR) } // do something }
app.use((ctx, next) => { return next().catch((err) => { let code = 500 let msg = 'unknown error' if (err instanceof CustomError || err instanceof HttpError) { const res = err.getCodeMsg() ctx.status = err instanceof HttpError ? res.code : 200 code = res.code msg = res.msg } else { console.error('err', err) } ctx.body = { code, msg } }) })
經過以上4步,拋出異常只用一行代碼就搞定。
錯誤拋出後,會統一由koa中間件來處理。經過對Error的繼承,咱們將錯誤細分爲http error和業務錯誤,從而能夠更好地處理錯誤返回。
日誌咱們使用 - log4js 插件
在異常捕獲中間件進行存儲
var log4js = require('log4js') log4js.configure({ appenders: { koa: { type: 'file', filename: 'koa.log' } }, categories: { default: { appenders: ['koa'], level: 'error' } } }); const logger = log4js.getLogger('koa'); logger.error({url: ctx.request.url, error: err, params: ctx.request.body});
出現異常時就會生成日誌文件,如圖
努力學習,提升代碼水平,少出異常!