什么是异常

做开发的基本都知道异常,像Android开发中常见的ANR异常、空指针异常,服务器开发中经常遇到的异常404,500异常,还有一些其他常见的异常,具体可见HTTP状态码

基本上这些异常可以总结为:已知异常未知异常

已知异常就是程序中能够预想到异常,比如:服务器接口开发中某个api接口需要5个参数,而用户传递的参数多余5个或者少于5个,这种错误就是已知错误。

未知异常就说程序中不能预想到的异常,比如:服务器接口开发中遇到了空指针而程序中又没有做相应处理就会抛出HTTP状态码为500的这种异常,这种就说未知异常。

为什么需要全局异常处理

当遇到异常时如果没有全局异常处理,一般是在相应的代码逻辑中添加异常捕捉(try ... catch)或者抛出(throw)处理。

这么做其实是有弊端的:

  1. 程序代码判断逻辑过长,可读性查,不方便后期维护。
  2. 代码耦合性高,每次出现异常都需要在不同的类、文件下写异常判断逻辑。

以上只是列举的几个弊端,为了解决以上的问题程序中添加全局异常的处理就很有必要了。

这里使用的是NodeJS+Koa2开发。在Koa中,中间件是无处不在,所以这里全局异常的处理也是通过中间件的方式去实现。

如何处理

在服务器接口开发中需要明确是生产环境还是开发环境

生产环境中如果出现异常需要将详细的异常信息上报同时将异常状态通过api返回给客户端处理

开发环境中如果出现异常则需要将详细的异常信息在开发工具的控制台显示,同时返回将异常状态通过api返回给客户端处理。

这里的区别就说生产环境开发环境,所以通过定义一个全局变量去判断即可。由于程序中全局变量可能不止一个,为了统一声明全局变量,我们将所有的全局变量放在一个文件中,统一去加载。

新建一个config.js,里面的environment:'dev'就是对环境声明的变量,这里dev表示开发环境prod表示生产环境

module.exports = {
    // prod 表示生产环境
    environment:'dev',
    database:{
        dbName:'book',
        host:'localhost',
        port:3306,
        user:'用户名',
        password:'密码',
    },
    // token 设置为1天
    security:{
        secretKey:"密钥,要记住不能弄丢了哦",
        expiresIn:60*60*24
    },
    host:'http://localhost:3000/'
}

配置了全局变量之后,在init.js文件的InitManager类中定义静态方法:

/**
 * 加载全局配置文件
 * @param {''} path 当前路径
 */
static loadConfig(path = '') {
    const configPath = path || process.cwd() + '/config/config.js'
    const config = require(configPath)
    global.config = config
}
/**
 * 加载全局异常
 */
static loadHttpException(){
    const errors = require('./http-exception')
    global.errs = errors
}

最后在app.js中完成初始化。

const app = new Koa()
InitManager.loadConfig()
InitManager.loadHttpException()

定义全局变量之后就需要制定返回的api异常描述

在服务器接口开发中,一个异常的返回结果,通常包含有:

  • msg:异常描述
  • errorcode:自定义的异常状态码
  • code:网络请求的状态码

两个code的区别:

  • errorcode :自定义的错误码,配合code定位具体的异常。
  • code:网络请求的状态码,如403 权限受限,而权限受限的原因有很多种,比如未登录或者登录了但是权限不足,这时候可以结合自定义的错误码和异常描述准确明确告知用户出错的原因。

定义异常描述之后就需要去判断程序是已知异常还是未知异常。

已知异常:可以定义HttpException继承Error这个类,只要是出现这异常属于HttpException都属于已知异常。

http-exception.js

/**
 * 默认的异常
 */
class HttpException extends Error{
    constructor(msg='服务器异常',errorCode=10000, code=400){
        super()
        this.errorCode = errorCode
        this.code = code
        this.msg = msg
    }
}

/**
 * 资源未找到提示
 */
class NotFound extends HttpException{
    constructor(msg, errorCode) {
        super()
        this.msg = msg || '资源未找到'
        this.errorCode = errorCode || 10000
        this.code = 404
    }
}

module.exports = {
    HttpException,
    NotFound
}

环境变量声明了、异常也做了处理,那么如何监听全局异常呢?

首先编写捕捉异常处理中间件catchError.js

const {HttpException} = require('../core/http-exception')

const catchError = async (ctx, next)=>{
    try {
        await next()
    } catch (error) {
        // 已知异常
        const isHttpException = error instanceof HttpException
        // 开发环境
        const isDev = global.config.environment === 'dev'
         // 在控制台显示未知异常信息:开发环境 不是HttpException 抛出异常
        if(isDev && !isHttpException){
            throw error
        }

        /**
         * 是已知错误,还是未知错误
         * 返回:
         *      msg 错误信息
         *      error_code 错误码
         *      request 请求的接口路径
         */
        if(isHttpException){
            ctx.body = {
                msg:error.msg,
                error_code:error.errorCode,
                request:`${ctx.method} ${ctx.path}`
            }
            ctx.status = error.code
        }
        else{
            ctx.body = {
                msg: '服务器出现了未知异常',
                error_code: 999,
                request:`${ctx.method} ${ctx.path}`
            }
            ctx.status = 500
        }
    }
}

module.exports = catchError

然后在app.js中加载中间件

const catchError = require('./middlewares/exception')
const app = new Koa()
// 全局异常中间件监听、处理,放在所有中间件的最前面
app.use(catchError)
app.listen(3000) 

以上就完成了全局异常的处理,接下来就是如何使用这个全局异常了。

这里以资源未找到为例。在查询数据库中,有的时候会出现数据找不到情况,这是用就可以抛出资源未找到的异常。

models/book.js

 /**
  * 获取书籍详情
  * @param {书籍id} bid
  */
 static async detail(bkid) {
     const book =await Book.findOne({
         where: {
             bkid
         }
     })
     if (!book) {
        // 通过全局异常的方式,抛出资源未找到的错误
         throw new global.errs.NotFound()
     }
     return book
 }

11-10 17:14