# 1. 前言
在之前的所有文章中,我们都默认所有处理逻辑都正常运行,不会出现异常,所以我们没有对异常进行处理。但是这显然是理想情况,真实项目中谁也不敢保证代码运行不会出现任何异常,为了防止代码跑崩,我们需要对异常进行统一处理。
不但要处理常规异常,我们也要对一些特殊情况进行兼容处理,使其能够返回可读性强的信息。例如,当请求的路由不存在时,此时会直接返回 Not Found
,而我们更希望返回 {status:404,msg:'接口不存在'}
;当请求携带的 Token
过期时,此时会直接返回 Authorization Error
,而我们更希望返回 {status:401,msg:'未授权'}
等等一些情况,所以我们有必要对这些情况进行统一处理一下。
# 2. 整理思路
我们要对进来的所有请求都做异常处理,那么编写一个中间件来干这件事是最合适不过的了。既然要编写自定义中间件,那么我们回到 「02.中间件」这一章节,编写中间件之前我们问自己两个问题:
我要拦截什么?是请求还是响应?
答:是响应。因为我们要处理的是路由响应请求的过程中如果发生错误,我们对其统一处理,所以我们要拦截的响应。
在什么阶段拦截?
答:我们希望请求进来后,经过层层中间件处理,再到响应即将出去,这中间任何一个环节出现异常,我们都要拦截处理,所以这个中间件应该处于所有中间件的最外层,这样才能确保请求一进来就先经过这个中间件,经过层层处理,最终响应出去也最后经过这个中间件。
OK,明确了这两个问题之后,我们就可以开始着手编写错误处理的中间件了。
# 3. 代码实现
# 3.1 创建文件
基于代码分层后目录结构,我们在 middleware
文件夹下创建一个名为 errorHandler.ts
文件,存放错误处理中间件的逻辑代码。
middleware/
├─ jwt.ts
├─ errorHandler.ts
└─ index.js
2
3
4
# 3.2 搭建中间件骨架
import * as Koa from "koa";
export default async (ctx: Koa.Context, next: Koa.Next) => {
try {
await next();
} catch (e) {
/*此处进行错误处理,下面会讲解具体实现*/
}
};
2
3
4
5
6
7
8
9
如上所述,我们要对响应进行拦截,所以我们需要将具体逻辑写在 next()
之后,因为我们要捕获在这个中间件之前所有环节中可能出现的错误,所以我们将 next()
用 try catch
包裹起来,在 catch
中编写具体的错误处理逻辑。
# 3.3 兼容 404 情况
当请求一条不存在的路由时,此时应该返回友好的 404
错误。由于路由不存在这种情况,它并不是一种代码运行异常,只是服务做出的默认的动作对我们来说不友好而已,所以不会触发 try catch
,为了统一在 catch
中处理方便起见,我们手动将其作为异常抛出,如下:
try {
await next();
/**
* 如果没有更改过 response 的 status,则 koa 默认的 status 是 404
*/
if (ctx.status === 404 && !ctx.body) ctx.throw(404);
} catch (e) {}
2
3
4
5
6
7
这样一来,我们就能在 catch
中处理了。
# 3.4 兼容 401 情况
同理,未授权情况也不会触发 try catch
,我们也需要手动地将其作为异常抛出,如下:
try {
await next();
if (ctx.status === 401 && !ctx.body) ctx.throw(401);
} catch (e) {}
2
3
4
# 3.5 错误统一处理逻辑
错误处理逻辑其实很简单,就是对错误码进行判断,并返回对应的信息。这段代码运行在错误 catch
中。
try {
await next();
if (ctx.status === 404 && !ctx.body) ctx.throw(404);
if (ctx.status === 401 && !ctx.body) ctx.throw(401);
} catch (e) {
const status = parseInt(e.status);
// 默认错误信息为 error 对象上携带的 message
let msg = e.message;
// 对 status 进行处理,指定错误页面文件名
if (status >= 400) {
switch (status) {
case 401:
msg = "未授权";
break;
case 404:
msg = "接口不存在";
break;
case 500:
msg = "服务发生异常";
break;
}
}
ctx.body = {
status,
msg,
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Ok,以上我们就编写完了错误处理中间件的所有逻辑。
# 4. 引入中间件
修改 middleware/index.ts
,引入 errorHandler
中间件,并将它放到洋葱模型的最外层,如下:
import * as Koa from "koa";
import * as bodyParser from "koa-bodyparser";
import errorHandler from "./errorHandler";
export default (app: Koa) => {
app.use(errorHandler); // 应用错误处理中间件
app.use(bodyParser());
};
2
3
4
5
6
7
8
# 5. 测试
当我们不携带
Token
请求时,返回的结果已经是我们经过处理后的样子了,如下:当访问一个不存在的接口时:
当我们在登录接口中手动抛出异常来模拟接口出错的情况时:
← 07.接口鉴权 01.原生JS实现栈结构 →