Vue 取消 axios 重复请求,将性能优化进行到底(你知道发出去的请求还能这么取消吗?)
目录前言如何取消请求?1、CancelToken.source工厂2、CancelToken构造函数全局请求优化1、如何判断重复请求?2、如何取消重复请求?3、配置拦截器前言在项目开发过程中,我们经常会遇到重复请求的场景,如果我们不对重复的请求进行处理,则可能会导致程序出现各种问题。比如重复的 post 请求可能会导致服务端产生两笔记录。那么重复请求是如何产生的呢?这里我们举 2 个常见的场景:假
目录
前言
在项目开发过程中,我们经常会遇到重复请求的场景,如果我们不对重复的请求进行处理,则可能会导致程序出现各种问题。比如重复的 post
请求可能会导致服务端产生两笔记录。那么重复请求是如何产生的呢?以下是2 个常见的场景:
-
假设页面中有一个按钮,用户点击按钮后会发起一个请求。如果未对该按钮进行控制,当用户快速点击按钮时,则会发出重复请求。
-
假设在查询某页面数据时,用户可以根据不同的查询条件来筛选数据。如果请求的响应比较慢,当用户在不同的查询条件之前快速切换时,就会产生重复请求。
类似于以上两种场景,我们可以将重复的请求(处于pending状态)取消,来优化性能、减少意外bug产生。
如何取消请求?
以下通过Vue项目中axios举例,阐述取消axios请求的方式。
对于浏览器环境来说,axios 底层是利用 XMLHttpRequest 对象来发起 HTTP 请求。如果要取消请求的话,我们可以通过调用 XMLHttpRequest 对象上的 abort
方法来取消请求:
let xhr = new XMLHttpRequest();
xhr.open("GET", "https://user/12345", true);
xhr.send();
setTimeout(() => xhr.abort(), 1000);
对于 axios 来说,我们可以通过 axios 内部提供的 CancelToken
来取消请求,其有两种方式,我们一一举例。
1、CancelToken.source
工厂
我们可以使用 CancelToken.source
工厂方法创建 cancel token
let CancelToken = axios.CancelToken;
let source = CancelToken.source();
// get请求
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
// post请求
axios.post('/user/12345', {
name: 'leo'
}, {
cancelToken: source.token
})
// 取消请求(传入的参数是可选的)
source.cancel('Operation canceled by the user.');
实例
如下,当我们点击搜索按钮,发送一个请求
<template>
<div class="main">
<el-button type="plain" @click="search">搜索</el-button>
</div>
</template>
<script>
export default {
name: "A",
data() {
return {
};
},
methods: {
search() {
let url = "https://lianghj.top:8888/api/private/v1/rights/list";
this.$axios.get(url).then((res) => {
console.log(res)
});
},
}
};
</script>
现在,我们设置低网速来模拟请求较慢(处于pending)的情况
此时,当我们连续多次点击按钮,将出现如下情形
在上一个请求还未响应完成时,我们又发送了重复的请求。其实在上一次请求响应之前,下一次重复请求都是不必要,会造成资源损耗、性能下降,甚至涉及到对数据修改操作时,多个重复请求可能产生意外的bug。
此时,取消重复请求就变得尤为重要。
那么,对于这个例子,我们如何使用 CancelToken.source
工厂方法,以达到取消重复请求的目的?
优化版
<template>
<div class="main">
<el-button type="plain" @click="search">搜索</el-button>
</div>
</template>
<script>
export default {
name: "A",
data() {
return {
source: null
};
},
methods: {
search() {
if(this.source){
// 如果上一次请求未完成,this.source不为null,则执行取消上一次请求的操作
this.cancel();
}
// CancelToken.source 工厂方法
let CancelToken = this.$axios.CancelToken;
this.source = CancelToken.source();
let url = "https://lianghj.top:8888/api/private/v1/rights/list";
this.$axios.get(url, {
// 传入cancelToken,使该请求可取消
cancelToken: this.source.token,
}).then((res) => {
// 请求响应完成,this.source为null,不影响下次相同的请求
this.source = null;
})
},
cancel(){
// 取消请求
this.source.cancel("取消重复请求!");
}
},
};
</script>
此时,如果我们连续多次点击按钮,结果如下
在上一次请求未响应完成,发起重复请求,则将上一次请求取消,以达到优化效果。
2、CancelToken
构造函数
我们也可以通过传递一个 executor 函数到 CancelToken
的构造函数来创建 cancel token
let CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 取消请求
cancel();
实例
如下方式也能达到取消重复请求的效果
<template>
<div class="main">
<el-button type="plain" @click="search">搜索</el-button>
</div>
</template>
<script>
export default {
name: "A",
data() {
return {
source: null
};
},
methods: {
search() {
if(this.source){ // 如果上一个请求未响应,则this.source不为null,执行取消上一次请求的操作
// 取消请求
this.source();
}
// CancelToken.source 工厂方法
let CancelToken = this.$axios.CancelToken;
let cancel;
let url = "https://lianghj.top:8888/api/private/v1/rights/list";
this.$axios.get(url, {
// 传入cancelToken,使该请求可取消
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
}).then((res) => {
this.source = null;
})
this.source = cancel;
},
},
};
</script>
我们知道,发出去的请求(处于pending阶段)可以使用以上两种方式进行取消或中断。那么,在真实的项目中,我们可能需要为全部的请求进行此项优化(取消重复请求),那又该如何去实现?
全局请求优化
这里会涉及到一个问题,我们为全局的请求进行此项优化时,如何判断是否为重复的请求呢?如我在发送1请求,紧接着去发送2请求,此时不算是重复请求,不需要取消;而当发送1请求,1请求还未响应完成时紧接着再去发送1请求,此时判定为重复请求,则执行取消上一次重复请求的操作。
1、如何判断重复请求?
当请求方式、请求 URL 和请求参数都一样时,我们就可以认为请求是一样的。因此在每次发起请求时,我们就可以根据当前请求的请求方式、请求 URL 地址和请求参数来生成一个唯一的 key,同时为每个请求创建一个专属的 CancelToken,然后把 key 和 cancel 函数以键值对的形式保存到 Map 对象中,使用 Map 的好处是可以快速的判断是否有重复的请求
let pendingRequest = new Map();
// 生成唯一的key
let requestKey = [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
let cancelToken = new CancelToken(function executor(cancel) {
if(!pendingRequest.has(requestKey)){
// 如果发送的请求不存在,则进行保存
pendingRequest.set(requestKey, cancel); // 保存cancel函数,以便后续执行取消请求操作
}
})
当出现重复请求的时候,我们就可以使用 cancel 函数来取消前面已经发出的请求,在取消请求之后,我们还需要把取消的请求从 pendingRequest
中移除。
2、如何取消重复请求?
因为我们需要为全局请求进行此优化,此时可以在拦截器上添加相关配置。
在配置请求拦截器和响应拦截器前,我们先定义3个功能辅助函数。
getRequestKey
:用于根据当前请求的信息,生成唯一的请求 key:
// 函数返回唯一的请求key
function getRequestKey(config) {
let { method, url, params, data } = config;
return [method, url, JSON.stringify(params), JSON.stringify(data)].join("&");
}
addPendingRequest
:用于把当前请求信息添加到pendingRequest对象中:
let pendingRequest = new Map();
function addPendingRequest(config) {
let requestKey = getRequestKey(config);
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, cancel);
}
});
}
removePendingRequest
:检查是否存在重复请求,若存在则取消已发的请求:
function removePendingRequest(config) {
let requestKey = getRequestKey(config);
if (pendingRequest.has(requestKey)) {
// 如果是重复的请求,则执行对应的cancel函数
let cancel = pendingRequest.get(requestKey);
cancel(requestKey);
// 将前一次重复的请求移除
pendingRequest.delete(requestKey);
}
}
3、配置拦截器
请求拦截器
axios.interceptors.request.use(
function (config) {
// 检查是否存在重复请求,若存在则取消已发的请求
removePendingRequest(config);
// 把当前请求信息添加到pendingRequest对象中
addPendingRequest(config);
return config;
},
function (error) => {
return Promise.reject(error);
}
);
响应拦截器
axios.interceptors.response.use(
function (response) => {
// 从pendingRequest对象中移除请求
removePendingRequest(response.config);
return response;
},
function (error) => {
// 从pendingRequest对象中移除请求
removePendingRequest(error.config || {});
if (axios.isCancel(error)) {
console.log("已取消的重复请求:" + error.message);
} else {
// 添加异常处理
}
return Promise.reject(error);
}
);
完整实例
import axios from "axios";
// 函数返回唯一的请求key
function getRequestKey(config) {
let { method, url, params, data } = config;
return [method, url, JSON.stringify(params), JSON.stringify(data)].join("&");
}
// 添加请求信息
let pendingRequest = new Map();
function addPendingRequest(config) {
let requestKey = getRequestKey(config);
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, cancel);
}
});
}
// 取消重复请求,移除重复请求信息
function removePendingRequest(config) {
let requestKey = getRequestKey(config);
if (pendingRequest.has(requestKey)) {
// 如果是重复的请求,则执行对应的cancel函数
let cancel = pendingRequest.get(requestKey);
cancel(requestKey);
// 将前一次重复的请求移除
pendingRequest.delete(requestKey);
}
}
// 请求拦截器
axios.interceptors.request.use(
function (config) {
// 检查是否存在重复请求,若存在则取消已发的请求
removePendingRequest(config);
// 把当前请求信息添加到pendingRequest对象中
addPendingRequest(config);
return config;
},
function (error) {
return Promise.reject(error);
}
);
// 响应拦截器
axios.interceptors.response.use(
function (response) {
// 从pendingRequest对象中移除请求
removePendingRequest(response.config);
return response;
},
function (error) {
// 从pendingRequest对象中移除请求
removePendingRequest(error.config || {});
if (axios.isCancel(error)) {
console.log("已取消的重复请求:" + error.message);
} else {
// 添加异常处理
}
return Promise.reject(error);
}
);
export default axios
此时连续多次点击按钮,重复请求(处于pending)将被取消,结果如下
完整实例可直接使用,在你项目里面试试吧。
更多推荐
所有评论(0)