vuex异步调用的takeLatest化(记一次项目中遇到的小问题)
前言: 第一次接触到takeLatest是在redux-saga, 包括takeLatest/takeEvery等
问题描述: 在一个有多级品类选择的页面 点击一个标签查询一次
后端接口在数据量比较大的情况下查询时间可能比较慢 并且接口的返回顺序不一定是前端页面调用的顺序
这个问题困扰了很久 解决办法有以下几种
- 跟产品沟通,用户每选择一个品类,让用户手动点击查询按钮,并提供loading蒙层 使用户没法在数据未返回之前继续操作
- 前端参数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) => { 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 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>
|