【笔记】(Koa 版)异步异常、全局异常、HTTP 异常处理

发布于 2020-02-16  99 次阅读


从0到1手把手教你用Node.js+KOA2打造超好用的Web框架

第4章 【深入浅出讲异常】异步异常与全局异常处理 笔记

一、异常理论与异常链

异常处理方式

  1. 局部异常处理(try-catch)
  2. 全局异常处理

异常链

抛出的异常被 try-catch 捕获后层层向上传递

func1();

function func1() {
    try {
        func2();
    } catch (err) {
        // Error: an error from func3...
        console.log(err);
    }
}

function func2() {
    try {
        func3();
    } catch (err) {
        throw err;
    }
}

function func3() {
    throw new Error("an error from func3");
}

二、异步异常处理

异步异常无法被 try-catch 捕获

function func() {
    try {
        setTimeout(() => {
            throw new Error("error");
        }, 1000);
    } catch (err) {
        // 不会执行, 且调用 func 会报错
        console.log("catch an error");
    }
}

结合 async 函数和 promise 处理异步异常

原理:将异步代码改写成同步代码

asyncExceptionCatcher();

async function asyncExceptionCatcher() {
    try {
        await asyncException();
    } catch (err) {
        // an asyncException
        console.log(err);
    }
}

function asyncException() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("an asyncException");
        }, 0);
    });
}

三、全局异常处理

局部异常处理繁琐,因此提出在一种方法,能够处理全局的异常

思想:利用中间件栈和异常链的特点(注意要严格按照“洋葱模型”即中间件函数都用 async 函数,才能保证顺序执行),在第一个中间件函数中处理异常

// 结合 async 函数和 promise 可以处理异步异常
const exceptionCatcher = async (ctx, next) => {
    try {
        await next();
    } catch (err) {
        ctx.body = "出现了一个错误";
    }
};

module.exports = exceptionCatcher;

四、HTTP 异常处理

通常 API 应该给前端返回明确的错误信息,因此需要一个机制来处理 HTTP 异常,并给前端返回错误信息

错误信息格式

{
    "message": "错误信息",
    "error_code": 1001,
    "request_url": "POST /xxx/xxx"
}

一般包含以下几个属性

  • message:错误信息
  • error_code:比 HTTP 状态码更详细的错误码,开发者自定义,便于前端开发者判断
  • request_url:当前请求的 url(访问的 API 接口)

错误类型

  1. 已知错误(明确的、已知的错误,比如开发者自己事先定义的错误类型)
  2. 未知错误(无意识的、未知的错误,比如第三方库或者程序本身抛出的错误 )

示例代码

// http-exception.js - 定义 HTTP 异常

// HTTP 异常基类
class HttpException extends Error {
    // 只有继承 Error, HttpException 的实例才能通过 throw 抛出
    constructor(message = "已知错误", errorCode = 1001, statusCode = 400) {
        super();
        this.msg = message;
        this.errorCode = errorCode;
        this.code = statusCode;
    }
}

// 特定的 HTTP 异常子类(增强代码复用性、可读性)
class ParameterException extends HttpException {
    constructor(message = "参数错误", errorCode = 1001) {
        super();
        this.msg = message;
        this.errorCode = errorCode;
        this.code = 400;
    }
}
// 其它特定的 HTTP 异常子类...

module.exports = {
    HttpException,
    ParameterException
};
// book.js - 抛出 HTTP 异常

const Router = require("koa-router");
const router = new Router();
const {
    HttpException,
    ParameterException
} = require("../../../core/http-exception");

router.get("/v1/book/latest", (ctx, next) => {
    const error = new ParameterException();
    throw error;
});

module.exports = router;
// excpetion-catcher.js - 全局异常处理,向前端返回错误信息(已知、未知错误)

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

// 结合 async 函数和 promise 可以处理异步异常
const exceptionCatcher = async (ctx, next) => {
    try {
        await next();
    } catch (err) {
        // 如果抛出的错误是 HttpException, 则说明是已知的错误
        if (err instanceof HttpException) {
            ctx.body = {
                error_code: err.errorCode,
                msg: err.msg,
                request: `${ctx.method} ${ctx.url}`
            };
            ctx.status = err.code;
        } else {
            ctx.body = {
                error_code: 5000,
                msg: "未知错误",
                request: `${ctx.method} ${ctx.url}`
            };
            ctx.status = 500;
        }
    }
};

module.exports = exceptionCatcher;

注意:全局异常处理中间件必须作为第一个中间件

const app = new Koa();
app.use(exceptionCatcher);
// 使用其它中间件...

请求结果


Stay hungry, Stay foolish. 求知似饥,虚心若愚。