取消请求的实现
- 重复请求取消
- 路由跳转组件未响应的请求取消(对于后端来讲 没有意义,前端来讲可能会限制并发请求个数)
- keepAlive组件的切换是否要清理请求(待实现)
重复请求取消
说到重复请求就要有一个判断依据什么样的请求是重复的(网上给出的大多数方案是通过url+参数作为唯一key,并不严谨)
答案很简单:组件内同一个请求函数发起的请求就是重复的(比如列表第一页,第二页在业务上就是重复请求,如果按网上的方法去判断是两个独立请求)
怎么去做呢
// @/api/useGetUserList.js
import request from '@/util/request/index.js'
import { ref } from 'vue'
const getUserList = (params) => {
return request.get('/v1/userList', { params })
}
export const useGetUserListApi = () {
const userList = ref([])
const getUserList = () => {
getUserList()
.then((res) => {
userList.value = res
})
}
return {
userList,
getUserList
}
}
// @/views/foo.vue
<template>
<button @click="setUserList">请求</button>
</template>
<script setup>
import { useGetUserListApi } from '@/api/useGetUserList.js'
import { createCancelableApi } from '@/api/createCancelableApi.js'
const { userList, getUserList } = useGetUserListApi()
// 不改变原有函数的行为 增加它的能力怎么去做 包装函数 特别注意上面提到组件内 那就需要使用到hook
const setUserList = createCancelableApi(getUserList)
</script>
// @/api/createCancelableApi.js
// 我们如何识别api函数
const getId = (mark) => {
let id = 0;
return () => `${mark ?? ''}${id++}`
}
// 给每个请求函数加上id
export const getApiId = getId('API')
// 每次执行请求打印的id 用于调试
const getReqId = getId('REQ')
export const createCancelableApi = (api) => {
const apiId = getApiId()
// 这个时候我们就能通过id来存储cancel函数了
return = (...args) => {
return api.call(null, ...args)
}
}
// 同时我们设想最小的操作元是组件
// @/hooks/useCancellationMap.js
import { getCurrentInstance } from "vue";
export const store = new Map()
export const useCancellationMap = () => {
const uid = getCurrentInstance().uid
const map = store.get(uid) ?? new Map()
store.set(
uid,
map
)
return map
}
// 这个时候就能保证每个组件一个map
// @/api/createCancelableApi.js
import { useCancellationMap } from '@/hooks/useCancellationMap.js'
export const createCancelableApi = (api) => {
const map = useCancelMap()
const reqId = getReqId()
// 这个时候就要给请求增加取消的能力
return = () => {
const config = {}
const controller = new AbortController();
config.signal = controller.signal
config.abort = () => {
console.warn('取消', reqId)
controller.abort()
}
config.apiId = apiId
config.reqId = reqId
config.map = map
return api.call(null, config)
}
}
// 此时已经给请求增加了取消的能力 下一步就是在请求拦截器中 拦截重复请求了
// @/util/request/interceptors/cancel.js
export const setupCancelInterceptors = (request) => {
request.interceptors.request.use(
(config) => {
const {
map,
apiId
abort
} = config
console.warn(
'请求',
config.reqId
)
// apiId对应的请求首次请求不会有取消函数
if (map?.has(apiId)) {
map?.get(apiId)?.()
// 取消后迅速删掉 用来区分是正常响应还是取消
map?.delete(apiId)
}
// 将apiId对应的取消函数设置在map中
map?.set(
apiId,
abort
)
return config
}
)
request.interceptors.response.use(
(res) => {
console.warn(
'响应',
res.config?.reqId
)
// 响应结束的请求从map中清掉
const {
map,
apiId
} = res.config
map?.delete(apiId)
return res ?? null
},
(error) => {
console.log(error)
}
)
}
// 到此我们解决了重复请求的问题
// 但是如果仔细去思考 假设页面上有两个地方分别调用了这个函数 我们根据业务需求还要区分 该怎么做呢
// @/views/foo.vue
<template>
<button @click="setUserList">请求</button>
<button @click="setUserList1">请求</button>
</template>
<script setup>
import { useGetUserListApi } from '@/api/useGetUserList.js'
import { createCancelableApi } from '@/api/createCancelableApi.js'
const { userList, getUserList } = useGetUserListApi()
// 还是在不改变原有函数的行为 增加它的能力 让他们变得不同
const setUserList = createCancelableApi(getUserList)
const setUserList1 = createCancelableApi(getUserList)
</script>
// @/api/createCancelableApi.js
import { useCancellationMap } from '@/hooks/useCancellationMap.js'
// 增加了一个位置id 从写api我们可以非常精细的感知
export const getPositionId = getId('POSITION')
export const createCancelableApi = (api) => {
const map = useCancellationMap()
const reqId = getReqId()
const pId = getPositionId()
// 这个时候apiId就不能作为判重的依据了 对应的请求拦截器里面也要使用这个key作为判断
const reqKey = apiId + pid
return = () => {
const config = {}
const controller = new AbortController();
config.signal = controller.signal
config.abort = () => {
console.warn('取消', reqId)
controller.abort()
}
config.reqKey = reqKey
config.reqId = reqId
config.map = map
return api.call(null, config)
}
}
// 至此 重复请求的判重工作完成
路由跳转组件未响应的请求取消
// 路由切换最直观的就是页面的变化 -> 映射到开发的角度就是组件的卸载
// @/hooks/useCancellationMap.js
import { getCurrentInstance, onUnmounted } from "vue";
export const store = new Map()
export const useCancellationMap = () => {
// 当组件即将卸载时,把map中存在的所有请求依次cancel, 并从全局的数据管理中移除掉
onUnmounted(() => {
map.values().forEach((cancel) => {
cancel()
})
map.clear()
store.delete(uid)
})
}
// 这个时候请求取消的全部功能完成
取消API实践场景不多,了解即可
后续会陆续分享vue3个人觉得最佳实践