前言在讲解如何处理异常之前,我们需要了解一下Koa的中间件机制。koa中间件机制分析koa的请求处理是一个典型的洋葱模型。下面是官方的图,这个模型的部分是中间件koa中间件的执行机制,是一个洋葱模型。每次有请求进来,前面先执行。middleware,遇到next,执行下一个中间件,重复,执行完所有中间件,从最后一个中间件向前执行例子:app.use(async(ctx,next)=>{console.log(1)awaitnext()console.log(2)})app.use(async(ctx,next)=>{console.log(3)awaitnext()console.log(4)})app.使用(异步(ctx,下一个)=>{控制台。日志(5)等待下一个()控制台。日志(6)})应用程序。use(async(ctx,next)=>{console.log(7)})//输出为1357642思考——中间件在异常处理中起到什么作用?异常捕获常见的抛出异常和错误类型代码语法非标准的JS报错异常程序运行过程中出现的一些未知异常HTTP错误自定义业务逻辑错误异常处理方式trycatch说到异常捕获,我们首先想到的一定是trycatch,在node下是怎么实现的?示例:constfunc=async(ctx,next)=>{try{awaitnext()}catch(){ctx.body={//returnexception}}}app.use(func)app.use(func1)app.use(func2)但是trycatch有个问题,它不能处理异步代码块内部发生的异常。可以理解为执行catch时,异常还没有发生)}回调方法fs.mkdir('/dir',function(e){if(e){/*处理异常*/console.log(e.message)}else{console.log('创建目录成功')}})Promise方法newPromise((resolve,reject)=>{syncError()}).then(()=>{//...}).catch((e)=>{/*处理异常*/console.log(e.message)})Promise也无法处理异步代码块中抛出的异常。throw方法Koa提供了ctx.throw(400)方法,可以让我们方便的抛出http错误,但是我们又想返回额外的信息?如何实现?ctx.throw(400,'namerequired',{user:user});如果需要定义多个业务逻辑错误码和指令,返回不同的代码,在controller层面,可以这样处理:router.get('/',(ctx,next)=>{if(checkToken(token)){constcode=ERROR_CODE.TOKEN_ERRORctx.body={code,msg:ERROR_MSG[code]}return}//dosomething})如果在模型层或服务器层,如何处理此类错误?通过定义返回值来解释错误,在controller中判断返回值并返回相应的错误码,例如:constsomefunc=async(token)=>{constres=awaittokenExpire(token)if(res){returnfalse}//dosomething}throwsanError,在controller中捕获异常,对比err.message返回相应的错误码,例如:constsomefunc=async(token)=>{constres=awaittokenExpire(token)if(res){throwError(ERROR_MSG.TOKEN_ERROR)}//dosomething}问题来了万一类型不对呢,文言文有很多种?每次都要if判断,烦不烦?process方法可以捕获任何异常(无论是同步代码块中的异常还是异步代码块中的异常)process.on('uncaughtException',function(e){/*Processexception*/console.log(e.message)});asyncError()syncError()错误事件监听方法constKoa=require('koa')constapp=newKoa()app.on('error',(err,next)=>{console.error('servererror',err)})constmain=ctx=>{ctx.throw(500)}app.use(main)app.listen(3000)中间件处理方式constKoa=require('koa')constapp=newKoa()app.use(async(ctx,next)=>{awaitnext().catch(error=>{console.log(error)});})constmain=ctx=>{ctx.throw(500)}app.use(main)app.listen(3000)优雅的异常处理方案!断言库-assert首先我们一个一个的使用一个断言库!为什么要使用断言?参考throw方法,我们通常需要针对不同的业务逻辑场景返回错误。例如:'用户名不为空'、'密码不能为空'、'开始日期大于有效期'、'密码输入错误'、'用户名不存在'...等如果使用throw方法,我们需要定义很多代码和msg来维护,比如:ERROR_CODE:ERROR_CODE={SOME_CUSTOM_ERROR:1001,EMPTY_PASSWORD:1002}ERROR_MSG:ERROR_MSG={1001:'somecustomerrormsg',1002:'Thepasswordcannotbeempty'}butusedassertions库之后,下面的代码我们就不用写了,不用维护代码,msgif(!ctx.request.body.password){throw(...)}if(!ctx.request.body.name){throw(...)}Justassert.ok(data.password,'密码不能为空')assert.ok(data.name,'用户名不能为空'),返回如下{"code":500,"msg":"passwordcannotbeempty","data":{},"success":false}定义HttpError和CustomError可以使用koa中间件实现我们的自定义方法继承自Error构造函数。functionCustomError(code,msg){Error.call(this,'')this.code=codethis.msg=msg||}ERROR_MSG[代码]||'未知错误'this.getCodeMsg=function(){return{code:this.code,msg:this.msg}}}util.inherits(CustomError,Error)functionHttpError(code,msg){if(Object.values(HTTP_CODE).indexOf(code)<0){throwError('notaninvalidhttpcode')}CustomError.call(this,code,msg)}util.inherits(HttpError,CustomError)抛出错误router.get('/HttpError',(ctx,next)=>{thrownewHttpError(HTTP_CODE.FORBIDDEN)})constsomefunc=async(token)=>{constres=awaittokenExpire(token)if(res){thrownewCustomError(CUSTOM_CODE).SOME_CUSTOM_ERROR)}//dosomething}koa中间件系统一catch住错误,并返回相应code,msgapp.use((ctx,next)=>{returnnext().catch((err)=>{letcode=500letmsg='未知错误'if(errinstanceofCustomError||errinstanceofHttpError){constres=err.getCodeMsg()ctx.status=errinstanceofHttpError?res.code:200code=res.codemsg=res.msg}else{console.error('err',err)}ctx.body={code,msg}})})通过以上4步,只需要一个需要一行代码来抛出异常。抛出http错误,thrownewHttpError(HTTP_CODE.FORBIDDEN)抛出统一的业务错误码。thrownewCustomError(CUSTOM_CODE.SOME_CUSTOM_ERROR)抛出特殊错误,使用assert断言库错误抛出后,统一由koa中间件处理。通过对Error的继承,我们将错误细分为http错误和业务错误,这样可以更好的处理错误返回。日志我们使用log4js插件在中间件??中存储异常varlog4js=require('log4js')log4js.configure({appenders:{koa:{type:'file',filename:'koa.log'}},categories:{default:{appenders:['koa'],level:'error'}}});constlogger=log4js.getLogger('koa');logger.error({url:ctx.request.url,错误:err,参数:ctx.request.body});当出现异常时,会生成日志文件,如图,最后好好学习,提高代码水平,减少异常!
