vuex的takeLatest化

vuex异步调用的takeLatest化(记一次项目中遇到的小问题)

  • 前言: 第一次接触到takeLatest是在redux-saga, 包括takeLatest/takeEvery等

  • 问题描述: 在一个有多级品类选择的页面 点击一个标签查询一次
    后端接口在数据量比较大的情况下查询时间可能比较慢 并且接口的返回顺序不一定是前端页面调用的顺序
    这个问题困扰了很久 解决办法有以下几种

    1. 跟产品沟通,用户每选择一个品类,让用户手动点击查询按钮,并提供loading蒙层 使用户没法在数据未返回之前继续操作
    2. 前端参数hash化 将请求参数hash化 将页面所有请求的数据通过参数的hash值保存在store 通过getter得到本次hash对应的数据
  • 以上两种方法 都很麻烦 第一个是需要更换用户体验 第二个会存储很多不需要的数据 且在特定场景下可能会造成性能问题

  • 于是参考了redux-saga的takeLatest实现一下代码

代码如下

1
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
28
29
30
31
32
33
34
35
36
37
38
39
export function takeLatest(action) {
let lastRun = null
return async (context, payload) => {
// 用户每次点击进来都会将当前时间赋給lastRun
const currentRun = Date.now()
lastRun = currentRun
const { commit, dispatch } = context

const commitLatest = (...args) => {
if (lastRun !== currentRun) {
throw new Error('ActionOutdatedError')
}
return commit(...args)
}
const dispatchLatest = (...args) => {
if (lastRun !== currentRun) {
throw new Error('ActionOutdatedError')
}
return dispatch(...args)
}

try {
return await action({
...context,
commit: commitLatest,
dispatch: dispatchLatest,
}, payload)
} catch (e) {
if (e.message === 'ActionOutdatedError') {
if (process.env.NODE_ENV === 'production') return
/* eslint-ignore */
console.log('actions aborted, latest action work')
} else {
// 接口层报错抛出
throw e
}
}
}
}

使用方式

1
2
3
4
5
6
7
actions: {
getData: takeLatest(/*参数为请求函数*/
async ({ commit }, payload) => {
commit('')
},
),
}

如果我不用vuex怎么办呢?

当然在页面上使用也是一样的 唯一不同的是在页面上我们需要考虑做下this的指向问题

1
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
function takeLatest(action, cb) {
let latest = null
return async function (...args) {
const current = Date.now()
latest = current
const context = this
const commit = function (...commitArgs) {
if (current !== latest) {
throw new Error('actionTakeLatest')
}
return cb.apply(context, commitArgs)
}
try {
return await action.call(this, {
originArgs: args,
commit,
})
} catch (e) {
if (e.message === 'actionTakeLatest') {
console.log('action aborted')
} else {
throw e
}
}
}
}

下面我们看个小demo

1
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
<template>
<p>{{ text }}</p>
<button @click="handleClick">点击测试takeLatest</button>
</template>

<script>
export default {
data() {
return {
text: '',
}
},
methods: {
/* takeLatest的参数尽量不要是使用箭头函数 因为箭头函数一旦定义this就已经确定了 */
handleClick: takeLatest(
async function ({ originArgs, commit }) {
const res = await new Promise(resolve => setTimeout(() => resolve(Math.random()), Math.floor(Math.random() * 5000)))
commit(res)
},
function (payload){
this.text = payload
},
),
},
}
</script>