koa2 相关的记录

koa2 相关的记录 第一节

koa 如何优雅的手动中断请求

事件起因

在开发自己的blog系统时,后端采用了koa2来处理,在使用过程中可以算是比较完美和舒服了。

但是在我进行一个最初的登录api时出现了一个问题,以下是这个api处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!username || !password) {
// 判断不过,直接结束后续操作,不想使用 if else 就是因为嫌看起来太麻烦了
}
try {
let user = await db.user.findOne({
where : {
username
}
});

if (!user) {
// 用户不存在,则也直接结束
}
// 如果存在直接返回用户
ctx.body = user;
} catch (err) {
logger.error(err);
}

我无法提前结束本次的请求,不论使用 return; return next() … 等等

分析

在我进行官网查询时,发现可以使用 nodejs 原生的 res.end() 来结束

在官方文档中 ctx.res 是 Nodejs 的 response,但是问题是并不支持 res.end(),也就是 ctx.res.end() 函数。


官方文档

Node’s response object.

Bypassing Koa’s response handling is not supported. Avoid using the following node properties:

  • res.statusCode
  • res.writeHead()
  • res.write()
  • res.end()

koa 不建议直接结束,于是我继续查找资料,发现了 koa.throw 这个函数,


官方文档

ctx.throw([status], [msg], [properties])
Helper method to throw an error with a .status property
defaulting to 500 that will allow Koa to respond appropriately.
The following combinations are allowed:

1
2
3
ctx.throw(400);
ctx.throw(400, 'name required');
ctx.throw(400, 'name required', { user: user });

For example ctx.throw(400, 'name required') is equivalent to:

1
2
3
4
const err = new Error('name required');
err.status = 400;
err.expose = true;
throw err;

Note that these are user-level errors and are flagged with
err.expose meaning the messages are appropriate for
client responses, which is typically not the case for
error messages since you do not want to leak failure
details.

You may optionally pass a properties object which is merged into the error as-is, useful for decorating machine-friendly errors which are reported to the requester upstream.

1
ctx.throw(401, 'access_denied', { user: user });

Koa uses http-errors to create errors. status should only be passed as the first parameter.


这个函数可以抛出错误,并结束本次请求,而 ctx,throw 其实就是一个错误类型,基于 js 最原始的 Error 类型,所以我们也可以使用 throw new Error 来手动抛出自定义错误。

并且通过 koa 中的 next 来,可以将错误拦截住,并进行进一步处理

1
2
3
next().catch((err)=>{ 
// do something
});

所以,接下来开始尝试更改和定义

修改代码

首先实现自定义错误

将自定义错误类型继承到标准 Error 类下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const util = require('util');

function CustomError(code, data = null) {
Error.call(this, '');
this.code = code;
this.body = data;
this.getInfo = function () {
return {
code : this.code,
body : this.body,
};
};
}
// inherits https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor
util.inherits(CustomError, Error);

module.exports = CustomError;

可以使用 ES6 的类继承(不懂的可以看阮一峰的es6教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CustomError extends Error {
constructor (code, data = null) {
super();
this.code = code;
this.body = data;
}
getInfo(){
return {
code : this.code,
body : this.body,
};
}
}
module.exports = CustomError;

自定义错误函数,可以自己增删参数。

如果想方便使用,不用每次导入,可以在每次请求进入后在中间件中处理

1
2
3
4
5
6
7
const CustomError = require('../utils/customError');
module.exports = async (ctx, next) => {
ctx.custom = {
error : CustomError,
};
await next();
};

然后在逻辑层可以这样使用

1
2
3
4
if (!username || !password) {
// 想结束本次请求,可以直接抛出即可
throw new ctx.custom.error(200, {message : '请输入用户名和密码'});
}

既然请求都结束了,那么接下来得处理koa,因为koa一旦捕获错误后,会自动处理成 4xx 或者 5xx 状态码;

当你的网站出现很多的 4xx5xx,对搜索引擎优化来说就会出现问题。

所以,那么接下来就我们自己处理 koa 的捕获。

使用 next().catch() 来捕获错误

1
2
3
4
5
6
module.exports = async (ctx, next) => {
return next().catch((err)=>{
ctx.status = err.code;
ctx.body = err.body;
});
};

捕获后,我们将 err 中的 code 赋值给 ctx.status ,改变 koa 的响应状态,同理,将 err 中的需要返回的数据赋值给 koa 的返回值

然后,我们就可以在前端收到一个200的结果。

完成

这样就可以手动优雅的结束此次请求。

-------文章到此结束  感谢您的阅读-------