咱们在讲解如何处理异常以前,须要先对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});
出现异常时就会生成日志文件,如图
努力学习,提升代码水平,少出异常!