koa中如何优雅地处理异常

8,774 阅读3分钟

一个良好的编码习惯必然离不开异常处理,本文将主要介绍如何在koa框架下面优雅地抛出错误,并统一处理返回。

前言

一个良好的编码习惯必然离不开异常处理,本文将主要介绍如何在koa框架下面优雅地抛出错误,并统一处理返回。

正文

koa是一个优秀的NodeJS web框架,当我们在开发web项目时,避免不了各种错误处理,包括Http错误以及自定义的业务逻辑错误。

在NodeJS中,我们可以这样抛出错误:

if (someCondition) {
  throw Error('some error')
}

不那么优雅的方式

http 错误

koa框架提供了ctx.throw(400)的方式,可以让我们方便地抛出http错误,但是如果你想同时返回一些有用信息怎么办?也许你会这么做:

ctx.status = 400
ctx.body = {
  msg: "some params is invalid"
}

业务逻辑错误

那么如果你是开发Restful API server,你肯定会需要定义若干业务逻辑错误码和说明,比如像下面这样:

code码 说明
0 ok
-1 服务器错误
4001 token过期

在controller层面,你也许可以这样处理(示例代码中大写的都是常量定义,之后不再赘述):

router.get('/', (ctx, next) => {
  if (tokenExpire(token)) {
    const errcode = ERROR_CODE.TOKEN_EXPIRED
    ctx.body = {
      errcode,
      msg: ERROR_MSG[errcode]
    }
    return
  }
  // do something
})

但是如果你想在service层面去抛出这个错误怎么办?这时候你也许会有2种处理方式:

  • 第一种,通过定义返回值来说明错误,在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_EXPIRED)
      }
      // do something
    }

那么有没有更好的方式呢?

更加优雅的方式

有没有一种更优雅的方式来抛出错误呢?答案是肯定的。我们希望无论在哪里,直接一行代码就可以抛出错误,并被正确处理,返回相应的错误码和信息。

利用koa中间件加上我们自定义的继承于Error构造器的方法便可以实现。

  1. 定义HttpError和CustomError
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)
  1. 抛出错误
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
}
  1. koa中间件统一catch住Error,并返回相应code,msg
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
    }
  })
})

通过以上3步,抛出异常只用一行代码就搞定。
当你需要抛出http错误throw new HttpError(HTTP_CODE.FORBIDDEN),当你需要抛出业务错误码throw new CustomError(CUSTOM_CODE.SOME_CUSTOM_ERROR)
错误抛出后,会统一由koa中间件来处理。通过对Error的继承,我们将错误细分为http error和业务错误,从而可以更好地处理错误返回。

这样一来,我们便可以在代码中去优雅地处理各种错误和异常了^_^

by the way

我搭了一个koa的脚手架,里面包含本文中提到的优雅地错误处理方式。欢迎star :)

下一篇:一次Node.js的性能问题排查