koa中如何优雅地处理异常

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

前言

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

正文

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

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

1
2
3
if (someCondition) {
throw Error('some error')
}

不那么优雅的方式

http 错误

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

1
2
3
4
ctx.status = 400
ctx.body = {
msg: "some params is invalid"
}

业务逻辑错误

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

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

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

1
2
3
4
5
6
7
8
9
10
11
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中判断返回值再返回相应错误码,比如:

    1
    2
    3
    4
    5
    6
    7
    const somefunc = async (token) => {
    const res = await tokenExpire(token)
    if (res) {
    return false
    }
    // do something
    }
  • 第二种,抛出Error,在controller中catch住异常,并对比err.message来返回相应错误码,比如:

    1
    2
    3
    4
    5
    6
    7
    const somefunc = async (token) => {
    const res = await tokenExpire(token)
    if (res) {
    throw Error(ERROR_MSG.TOKEN_EXPIRED)
    }
    // do something
    }

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

更加优雅的方式

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

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

1.定义HttpError和CustomError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)

2.抛出错误

1
2
3
4
5
6
7
8
9
10
11
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
}

3.koa中间件统一catch住Error,并返回相应code,msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 :)

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器