开发日记(02) - js 异步任务队列
开发日记(02) - js 异步任务队列
2021-01-31 20:40:22
0️⃣ 问题 ❓
算是之前项目遗留下来的一个问题。一直困扰着我。
还是关于 uni-app
以及 Vue
项目的网路请求,有这么一个需求,项目内有一个全局使用到数据,我们称为**“数据字典”**。需要在项目一打开就加载进来,存入到 Vuex
,后续使用就不需要再请求网络了,使用的时候先判断 Vuex
内有没有数据,没有就去请求,有就用现成的。一般像这样的数据不需要经常更新。 有点类似于项目的全局配置,一打开就需要请求,然后在特定的页面需要用到。
因为是全局需要用到,所以我们在 App.vue
的 onLaunch
应用生命周期(uni-app
的应用生命周期,与 Vue
的 mounted
生命周期类似)进行一次请求
数据字典就是用来格式化类似性别,订单状态的,比如后台返回一个订单列表,订单状态为 1,2,3,4,5...
前端不可能显示为纯数字,这个时候可以根据后台给到的解释进行判断显示。不过这种方法有一个缺点,就是每增加一种状态前端都得改代码。
数据字典就是一种比较好的解决方式,订单状态全部放在后端维护,前端使用数据字典配置好的状态说明进行格式化显示就很灵活了。
${app}/src/App.vue
import { getDictByKey } from '@/store/util'
export default {
onLaunch() {
console.log('App Launch')
getDictByKey()
},
}
在页面中这么用
${app}/src/pages/foo/foo.vue
import { getDictByKey } from '@/store/util'
export default {
async onLoad() {
// 请求类型信息
const typeList = await getDictByKey('title_type', 'all')
if (typeList && typeList.length) {
this.typeList = typeList
}
},
}
这样用效果是可以实现的,但是我在 foo.vue
页面刷新页面的时候,就会触发两次相同的网络请求,拉了两遍请求数据字典的接口,因为 App.vue
有一次请求,foo.vue
也有一次网络请求。
这个接口数据量相对来说比较大,就会卡顿一下,拉取两次本来也是不正确的,虽然需求完成了,但是出于码农的强迫症和“职业道德”,这个问题不能蒙混过关,一定要解决它。
1️⃣ 解决方案
本地存一份数据
这是我们最开始用的解决方案,因为数据不经常更新,我们就把这个接口返回的 json
存为文件,然后在项目直接引入,只在 App.vue
进行一次请求更新。在页面内使用不请求。
一开始倒是没什么问题,项目经过迭代之后,数据字典模块也随之更新了,这样就造成了,假如我在 foo.vue
页面刷新数据,网络请求还没回来,本地没有的数据就显示空白。
异步任务队列
有看过关于任务队列的介绍,像 mq
、kafka
,都是用来做消息队列的。 mysql
的事务隔离模式也有类似的。异步任务队列有点类似于 “串行化”,画张图大家感受下
不管有多少个人问我要数据,我都把你们的请求存起来,我去拿数据,等我拿到了,我自己存起来,再一个个给你们。这样网络请求只发送一次,但是项目内同时可以有多个请求,类似的操作不仅仅在请求网络的时候能用到。
2️⃣ 代码实现异步任务队列
老规矩,直接上完整代码,代码不多,已经在项目内用上了,没有发现问题。拷贝需要修改成你自己的业务逻辑。
下面来慢慢分析代码,先说实话,点子是我自己想的,我实现不出来,就去网上找了蛮久,找到了一个看起来不错的优雅实现(其实就是代码比较少,改起来简单点 😁)
import store from '@/store/index'
import { handleApiRequestException } from '@/util/handle-error'
// 任务队列
const queue = []
/**
* @name 通过字典的key值获取字典的value(添加任务)
* @param {string} key 数据字典的 key 值
* @param {*} value 用来标识请求所有还是单个值
*/
export function getDictByKey(key, value) {
return new Promise((resolve) => {
const task = { resolve, key, value }
queue.push(task)
if (queue.length === 1) {
_next(task, true)
}
})
}
/**
* @name 执行任务
* @param {object} nextPromise 任务对象
* @param {boolean} first 是否是第一个任务
*/
async function _next(nextPromise, first) {
const { resolve, key, value } = nextPromise
if (!store.state.dict.length) {
try {
await store.dispatch('getDictList')
resolve(store.getters.filterDict(key, value))
} catch (error) {
handleApiRequestException(error)
resolve(value === 'all' ? [] : '')
}
} else {
resolve(store.getters.filterDict(key, value))
}
let task = queue.shift()
if (first) {
task = queue.shift()
}
task && _next(task)
}
我们从第一行开始看起
import
进来了两个东西,一个是 Vuex
实例,一个是错误处理。
queue
这个就是我们要的任务队列了。我们需要数据的请求,一个个往里面添加。后面执行完成了的任务会通过 shift
弹出去。
getDictByKey
就是请求,来看看页面怎么用的
import { getDictByKey } from '@/store/util'
// 获取时间单位类型
this.dateTypeList = await getDictByKey('time_type', 'all')
这里用到了 Promise
对象的一个特性,没有 resolve
就会一直阻塞。
_next
方法用来启动执行任务,传入一个任务和是否为第一个任务的标识,
-
取出任务
-
判断
Vuex
数据源是否已经有值如果有
- 直接
resolve
同步函数执行的结果
如果没有
- 执行网络请求
- 请求成功存入
Vuex
数据仓库 resolve
同步函数执行的结果- 请求失败,
resolve
空数据,同是进行错误处理(toast 提示)
- 直接
-
拿到下一次任务
-
判断是不是第一次任务,如果是需要弹出,因为第一次任务已经执行过了,并且
resolve
了 -
判断任务是否存在,存在就继续使用
_next
任务执行函数执行第一步,没有任务就执行完毕了
3️⃣ 总结
是不是恍然大悟,还可以这样???😲😲😲,是的就是这么简单。当然如果你足够牛逼,可以加个异步任务出错重试,超时啥啥的,现在这样我们的项目够用了。
还有啊,如果你认真看了我的文章,解决了你的问题,我建议你关注下我,至少给我点个赞。你不要“不知好歹”,毕竟我还有很多问题的解决方案。
你们要是但凡有一个人给我提个问题,也不至于我王者荣耀周末“五连跪”!😤
📔 开发日记系列
只记录些平时开发觉得有用的东西,有问题请务必斧正,拜托了 🙏🙏🙏
关于我
SunSeekerX,
全栈开发、区块链开发、移动端开发、前后端开发、NodeJS 开发、小程序、uni-app
开发、等
喜欢探讨技术实现方案和细节,完美主义者,见不得 bug
。
Github:https://github.com/SunSeekerX
个人博客:https://yoouu.cn/
个人在线笔记:https://doc.yoouu.cn/