Koa异常处理说明

Koa异常处理说明

作者:zjruan 日期:2017/07/10

使用篇:

Demo:

// mc/homework
// 个人中心-我的课程-课后作业

get_homework() {
    // 你的处理逻辑 start
    ...
    let lessonId = this.query.lessonid;
    let resBody = yield courseMemberCenterApi.getHomework(lessonId);
    ...

    yield this.render('page/mc/homework-index', { title: '课后作业', leftNavIndex: 'myCourse', exampaper: exampaper })
    // 你的处理逻辑 end
}

一、Controller 中 try catch 的使用

由于项目在Koa顶层有统一的异常处理,因此正常情况下,我们是需要编写 try catch 的,除非我们需要做对这个异常进行编辑,或者是这个异常我们自己能处理,不希望该异常冒泡到统一异常处理函数

    // 1、对异常进行修改,并抛出修改后的异常
    get_homework() {
        try{
            // 你的处理逻辑
            ......
        }catch(error){
            error.message = '这个异常我知道,是后端数据库中的脏数据引起的!';
            throw error;    // 抛出处理后的异常
        }
    }

    // 2、已知异常,自己处理而不希望抛出到上层
    get_homework() {
        try{
            // 你的处理逻辑
            ......
        }catch(error){
            error.message = '这个异常我知道,是后端数据库中的脏数据引起的!';
            // console.log('我知道这么处理,而且我不想让别人知道');
            // this.redirct('login')    // 异常处理逻辑
        }
    }

二、如何生成自己的异常

异常在各种开发语言中都有广泛的应用,适时的抛出有效的异常可以帮助开发者理解程序逻辑。例如:

get_homework() {
    // 你的处理逻辑 start
    ...
    let lessonId = this.query.lessonid;
    // 由于后续逻辑严重依赖 lessonid,当 lessonid 无效时,后续操作便没有意义,而且耽误时间。
    // 因此对依赖参数进行有效性检查是必要的
    if(!lessonId){
        this.throw('400', 'lessonid 无效');
    }

    let resBody = yield courseMemberCenterApi.getHomework(lessonId);
    ...

    yield this.render('page/mc/homework-index', { title: '课后作业', leftNavIndex: 'myCourse', exampaper: exampaper })
    // 你的处理逻辑 end
}

抛出异常语法:

    // ctx 环境变量
    // statusCode http错误码,错误码是有限制的,无效的错误码会被替换为500,错误码请看附录
    // message 异常描述,可选
    // param 异常携带的参数, 可选
    ctx.throw(statusCode [, message], [param])

koa uses http-errors to create errors,点过去看看。


Koa 统一异常处理

统一异常处理的好处不用多说,那么在 Koa 中,如何使用?

我们先查看 Koa 的 API 文档,找到错误处理段落,原文如下:

Error Handling

By default outputs all errors to stderr unless app.silent is true. The default error handler also won't outputs errors when err.status is 404 or err.expose is true. To perform custom error-handling logic such as centralized logging you can add an "error" event listener:

app.on('error', err =>  
 log.error('server error', err)  
);

If an error is in the req/res cycle and it is not possible to respond to the client, the Context instance is also passed:

app.on('error', (err, ctx) =>
 log.error('server error', err, ctx)
);

When an error occurs and it is still possible to respond to the client, aka no data has been written to the socket, Koa will respond appropriately with a 500 "Internal Server Error". In either case an app-level "error" is emitted for logging purposes.


因此,我们只需要给 Koa 添加一个异常监听事件并且处理这个事件就可以了。

// 注意:以下代码为伪代码,无法直接复制运行

// app.js
import errorHandler from 'middleware/errorHandler.js'
...
app.on(error, errorHandler);


// errorHandler.js
module.exports = function(err, ctx) {
    // 未知异常状态,默认使用 500
    if(!err.status) err.status = 500;
    ctx.status = err.status;

    // 获取客户端请求接受类型
    let acceptedType = ctx.accepts('html', 'text', 'json');

    switch(acceptedType){
        case 'text': 
            ctx.type = 'text/plain';
            ctx.body = err.message;
            break;
        case 'json': 
            ctx.type = 'application/json';
            ctx.body = {error: err.message}
            break;
        case 'html': 
        default: 
            // 默认返回页面
            ctx.type = 'text/html';
            ctx.redirect(getUrl(err.status));
            break;
    }

    /**
    * 根据 Http 状态码,获取重定向页面
    *
    * param {number} status http状态码
    */
    function getUrl(status){
        switch(error.status){
            case 401: url = '401.html'; break;
            case 404: url = '404.html'; break;
            case 500: url = '500.html'; break;
            case 502: url = '502.html'; break;
            default:
                if(err.status < 500) {
                    url = '40x.html';
                } else {
                    url = '50x.html';
                    ctx.redirect('50x.html'))
                }
        }
        return url;
    }
} 
    

到了这一步应该就差不多了,思路是添加一个全局的异常监听事件,当异常发生时,对其进行统一的处理。

根据客户端接受数据类型,区分返回格式,对于请求页面的,我们最好能重定向到相应的页面,区分 404 和 500 等异常页面,对用户来说会更加友好。

由于异常页面数量有限,直接写成静态页面即可,虽然节约的性能并不明显,主要是 UI 可能会提供不同风格的异常页面,方便定制。

404 页面

所谓的统一异常处理,无非就是在程序的最外层,包上一层try catch,以至于所有的异常都会被这个catch给catch住。但是我们注意上面的那段文档中有一句话:

default error handler also won't outputs errors when err.status is 404 or err.expose is true

大致意思是 404 是不会抛出异常的,也就是说我们写的这个 errorHandler 不会处理 404页面
那怎么办?少里 404 的异常处理还叫统一的异常处理么?

显然不是,所以我们得想办法解决。那为什么404不抛出异常呢?因为 404 是服务器没有找到访问目标,也就是 Koa 路由没有匹配到对于的url,并不是执行异常,因此无法被 try catch catch住。
因为它不是异常,所以不能被catch住,那么我们就为的给它抛出异常,这样问题不就解决了。

我们需要在 koa-router 加载之前,添加一个中间件,如下:

app.use(function* (next) {
    yield* next;
    if (this.response.status === 404 && !this.response.body) this.throw(404);
});

这样相当于在 koa-router 后面添加了一层判断。当路由匹配失败后,判断返回是不是404,如果是的,我们就手工抛出 404异常, 这样就能被我们的统一异常处理方案处理了。

附录:

Status Code Constructor Name
400 BadRequest
401 Unauthorized
402 PaymentRequired
403 Forbidden
404 NotFound
405 MethodNotAllowed
406 NotAcceptable
407 ProxyAuthenticationRequired
408 RequestTimeout
409 Conflict
410 Gone
411 LengthRequired
412 PreconditionFailed
413 PayloadTooLarge
414 URITooLong
415 UnsupportedMediaType
416 RangeNotSatisfiable
417 ExpectationFailed
418 ImATeapot
421 MisdirectedRequest
422 UnprocessableEntity
423 Locked
424 FailedDependency
425 UnorderedCollection
426 UpgradeRequired
428 PreconditionRequired
429 TooManyRequests
431 RequestHeaderFieldsTooLarge
451 UnavailableForLegalReasons
500 InternalServerError
501 NotImplemented
502 BadGateway
503 ServiceUnavailable
504 GatewayTimeout
505 HTTPVersionNotSupported
506 VariantAlsoNegotiates
507 InsufficientStorage
508 LoopDetected
509 BandwidthLimitExceeded
510 NotExtended
511 NetworkAuthenticationRequired

你可能感兴趣的:(Koa异常处理说明)