简洁优雅地处理你的 express 请求:支持请求复用,重复请求限制,数据格式化和数据注入(且 ts 类型完备。 项目地址

安装

1
npm install express-action

示例

建议同时配合 Demo 源码 来阅读文档,就那么几个 api,一学就会!

配置

创建 makeAction.js, 你可以在这个步骤里定义项目返回数据的格式化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import createActionMaker from 'express-action'

export default createActionMaker({
// 统一格式化响应内容
response(res, obj) {
const { data = null, msg = '', error = false } = obj
return res.send({ data, msg, error })
},
// 统一处理错误
onError(e, res) {
console.error(e)
res.status(500).end()
},
})

一个最简单的请求

ts 彩蛋:如果配合 createActionMaker 中的 ts 类型,你还会获得返回结果的类型提示和校验

1
2
3
import makeAction from '@/libs/makeAction'

export const getBooks = makeAction(() => ({ data: books }))

以上代码即实现了一个最简单的请求,当用户请求 /api/books 的时候会得到:

1
2
3
4
5
{
"data": [{ "id": "1", "name": "book1" }, { "id": "2", "name": "book2" }],
"msg": "",
"error": false
}

可以看到,返回数据被你配置的 response 处理过了

API

injectData

在 handle 函数的回调参数内注入数据,以鉴权这个场景为例:

首先定义一个注入器:injectUser

1
2
3
4
5
6
7
8
9
10
11
12
13
import { InjectError } from 'express-action'

async function injectUser(req) {
// 假设 token 存在于 header 中
const token = req.get('token') || ''
const user = await getUser(token)
if (user) return { user } // action reject 必须返回一个对象,因为只有 key-value 这种形式才能作为注入数据
/// 主动抛出一个 InjectError, 插件会把构造参数交给 response 处理。你可以利用该特性来做鉴权
throw new InjectError({
msg: '请先登录',
error: true,
})
}

注意,InjectError 传入的构造参数会交给配置中的 response 来处理,所以它同样要符合你的 response 函数要求的数据格式

之后你可以在任何需要用到 user 这个信息的请求中注入它

ts 彩蛋:如果忘记 injectUser 就直接使用 user 是无法通过 ts 类型校验的 & user 拥有类型提示

1
2
3
4
5
6
7
8
9
10
11
export const getBooksWithAuthentication = makeAction(
{
injects: [injectUser],
},
// 上面传入的 injects 返回的数据都会被注入到下面
// 如果注入失败,比如这个例子的失败场景就是获取不到用户信息,那么请求会返回 injectUser 抛出的 InjectError 信息
async ({ user }) => {
const books = await getBooksByUser(user.name)
return { data: books }
},
)

shareRequestKey

在一些并发场景下,多个请求请求了同一份资源,当你不希望每一个请求都走一遍 handle(handle 里一般是一些比较消耗资源的操作,如数据库查询),你可以这样复用它们:

1
2
3
4
5
6
7
8
9
10
11
export const getBooksById = makeAction(
{
// 如果发现有相同的 req.params.id 正在请求中,则会复用之后的请求,也就是说这些相同的请求将得到一个相同的结果
shareRequestKey: (req) => req.params.id,
},
({ req }) => {
// 当有重复请求发生的时候,这里的代码只会被执行一次
const book = books.find((book) => book.id === req.params.id)
return { data: book }
},
)

uniqueRequestOption

这项配置一般用于修改操作的时候,可以很简单的实现不允许重复提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const setBook = makeAction(
{
uniqueRequestOption: {
// 如果发现有相同的 req.params.id 正在请求中,则会拦截掉后面的并返回 errorData
key: (req) => req.params.id,
errorData: { msg: '请不要重复提交', error: true },
},
},
({ req }) => {
// 当有重复请求发生的时候,这里的代码只会被执行一次
const book = books.find((book) => book.id === req.params.id)
if (book) book.name = '23333'
return { data: book, msg: '修改成功' }
},
)

注意,uniqueRequestOption.errorData 交给配置中的 response 来处理,所以它同样要符合你的 response 函数要求的数据格式

测试

上面的介绍基本包含了本插件的所有用例。你也可以将整个项目 clone 到本地,它也是一份推荐的 express-action 项目结构。

1
npm run start

通过上面的命令来启动它,感兴趣的话可以再翻翻源码,里面有不少有趣的实现,尤其是 ts 的类型推导部分

其他

错误处理

只有未知的异常才会交给配置项中的 onError 来处理,uniqueRequestOption 中的 errorDataInjectError 都是交给 response 处理

状态码

response 不仅仅可以用来统一响应数据的格式,需要的话还可以根据你定义的数据类型来返回不同的状态码

1
2
3
4
function response(res, obj) {
const { data = null, msg = '', error = false, code } = obj
return res.send(code || 200, { data, msg, error })
}

关于 shareRequestKey 和 uniqueRequestOption

其实这两项配置归根结底是为了处理各类异步任务而产生的,如果你使用了 redis 之类的工具,把各种异步操作转成了同步后那一般来说就用不到它们了。总之,简单的事情就用简单的办法


 评论