借用掘友(橙某人)的前言解释: 说起这个重复请求,感觉用到得比较少,心理总有怎么一种想法就算多请求一次又能怎么样,服务器会塌吗?页面会挂吗?明显不会嘛,不要大惊小怪,哈哈哈。再说没事怎么会多发重复的请求呢?不可能的。
而且做取消重复请求操作,其实取消后的请求还是有可能会到达了后端,只是前端浏览器不处理而已,但是呢,哎,我们还是得做做工作,不,非做不可,所谓以防万一,严谨,程序猿需要严谨!!!
发生重复请求的场景一般有这两个:
- 快速连续点击一个按钮,如果这个按钮未进行控制,就会发出重复请求,假设该请求是生成订单,那么就有产生两张订单了,这是件可怕的事情。当然一般前端会对这个按钮进行状态处理控制,后端也会有一些幂等控制处理策略啥的,这是个假设场景,但也可能会发生的场景。
- 对于列表数据,可能有tab状态栏的频繁切换查询,如果请求响应很慢,也会产生重复请求。当然现在很多列表都会做缓存,如Vue中用
<keep-alive />。
正言:
判断重复请求并储存进队列
首先我们要收集请求中的接口并判断哪些请求是重复请求,我们才能取消它,那么如何判断呢?很简单,只要是请求地址、请求方式、请求参数一样,那么我们就能认为是一样的。而我们要存储的队列里面的数据结构很明显应该是以键值对的形式来存储,这里面我们选择 Map 对象来操作。
// axios.js
const pendingMap = new Map();
/**
* 生成每个请求唯一的键
* @param {*} config
* @returns string
*/
function getPendingKey(config) {
let {url, method, params, data} = config;
if(typeof data === 'string') data = JSON.parse(data); // response里面返回的config.data是个字符串对象
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}
/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
* @param {*} config
*/
function addPending(config) {
const pendingKey = getPendingKey(config);
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, cancel);
}
});
}
取消重复请求并出删除队列
// axios.js
/**
* 删除重复的请求
* @param {*} config
*/
function removePending(config) {
const pendingKey = getPendingKey(config);
if (pendingMap.has(pendingKey)) {
const cancelToken = pendingMap.get(pendingKey);
cancelToken(pendingKey);
pendingMap.delete(pendingKey);
}
}
添加拦截器
// axios.js
function myAxios(axiosConfig) {
const service = axios.create({
baseURL: 'http://localhost:8888', // 设置统一的请求前缀
timeout: 10000, // 设置统一的超时时长
});
service.interceptors.request.use(
config => {
removePending(config);
addPending(config);
return config;
},
error => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
response => {
removePending(response.config);
return response;
},
error => {
error.config && removePending(error.config);
return Promise.reject(error);
}
);
return service(axiosConfig)
}
我们在上面提到的 getList() 方法里面简单模拟连续发出了三次重复请求,然后把浏览器设置成3G模式,就能看到效果啦。
// App.vue
function getList() {
getListAPI().then(res => {console.log(res)})
setTimeout(() => {
getListAPI().then(res => {console.log(res)})
}, 200);
setTimeout(() => {
getListAPI().then(res => {console.log(res)})
}, 400);
}
需要注意,上面说了取消正在请求中的接口,说明这接口有可能已经到达后端了,只是后端响应慢,所以如果你的接口响应比较快的话,就很难看到效果;如果你是自己搭建的服务,只要通过接口返回时延时下就可以看到效果;又或者通过浏览器的network调整网络速度也可以哦。
对于取消后的请求我们也应该有个合理的处理,不能就不管了,尽可能的达到代码可控的底部,它会被归类到异常里面,下面会说到( ^ω^ )。
配置化(有的接口需要重复请求:config.repeat_request_cancel)
之所以弄成配置化取消重复请求,是因为可能存在一些特殊变态的场景情况,是需要重复请求,如输入实时搜索、实时更新数据等,反正就是可能存在吧。( ̄y▽ ̄)~*
// request interceptor
service.interceptors.request.use(
config => {
if (config.method === 'upload') {
config = {
...config,
...{
method: 'post',
data: config.data,
methodType: 'upload',
headers: {
'Content-Type': 'multipart/form-data' // 需要指定上传的方式
},
transformRequest: [function() {
return config.data
}]
}
}
}
removePending(config)
config.repeat_request_cancel && addPending(config)
return config
},
error => {
// do something with request error
console.log('error', error) // for debug
return Promise.reject(error)
}
)
我们在上面增加了一个自定义配置的参数,现在每个API方法就能拥有两个参数,第一个参数传递的是axios原本的一些配置,第二个参数就是我们自己的一些自定义参数了,如我们定义 repeat_request_cancel 来控制是否开启取消重复请求的功能。后续更多功能,我们也能添加进其中,相当于可定制化每个API方法,是不是很棒!!!
// 营销信息管理-列表
// 配置repeat_request_cancel的Boolean类型来判断
export function getProductElementInfoUrlByReq(data, loading = true) {
return request({
url: '/xxxxx/xxxxxxx',
method: 'post',
data: data,
loading,
repeat_request_cancel: true
})
}
over!