vue 配置axios拦截重复的请求
描述: 拦截重复请求就是防止一时间发起多个一样的请求造成系统卡顿, 比如网速较慢时用户频繁点击发起请求, 这边我也看了很多博客学习如何配置比较好,然后看了又 不太明白 axios是如何取消之前的请求的, 原理是怎么样?? 很多博客都没说,只是贴了代码, 官网也有,但是还是只是明白了大概, 所以我这边还是自己总结一下:可以先去看看官网描述:官网描述大致原理(后面会贴源码分析,当然,那是大佬分析的,不
描述: 拦截重复请求就是防止一时间发起多个一样的请求造成系统卡顿, 比如网速较慢时用户频繁点击发起请求, 这边我也看了很多博客学习如何配置比较好,然后看了又 不太明白 axios是如何取消之前的请求的, 原理是怎么样?? 很多博客都没说,只是贴了代码, 官网也有,但是还是只是明白了大概, 所以我这边还是自己总结一下:
可以先去看看官网描述:
官网描述
大致原理
(后面会贴源码分析,当然,那是大佬分析的,不是我这个菜鸡):
其实就是 我们 通过官网提供的 CancelToken.source 工厂方法创建 cancel token 或者 通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token, 然后我们请求带上这个cancleToken, 然后在需要取消的请求后面手动调用 cancel方法进行取消, 可能这样说不是很清楚吧,对于上面两种情况下面在详细说一下:
1. CancelToken.source 工厂方法创建 cancel token
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// 返回一个包含token和cancel方法的对象
这样会返回一个token和cancel方法的对象,我们就在请求的方法添加上cancleToken, 比如页面你有个请求,这边拿官网的来演示
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// 你调用了一个get方法
axios.get('/user/12345', {
cancelToken: source.token // 带上cancelToken
}).catch(function(thrown) {
// 取消后进入这里
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
// 或者你是调用post
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token // 带上
})
// 取消请求(message 参数是可选的)
// 这里调用了取消请求,会把上面的请求取消掉, 当然这里只是演示用,实际饿哦们不是在这调用
source.cancel('Operation canceled by the user.');
2. 通过传递一个 executor 函数到 CancelToken 的构造函数
这个是通过构造函数生成cancleToken和把cancle取消函数传给我们自己定义的变量
const CancelToken = axios.CancelToken; // 构造函数
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c; // 赋给我们定义的cancle,注意这个 c 其实是个函数
})
});
// 在你需要取消的地方调用取消函数
cancel(); // 调用取消函数
大致就这样啦, 那么项目怎么全局配置,而不是这样一个请求设置一次??
这边先贴我的代码哈, 大部分逻辑也是参考网上的
说明: 我这里是通过一个数组保存请求, 然后每次在拦截里面判断是否是同一个请求, 如果是则取消上一个请求, 那么判断依据我这里是 判断了 请求方法 method, 请求地址 url和请求参数(params或者data) 要全部 一样 才算同一个请求, 不然有些请求地址一样只是参数不一样.
setting1.js
import axios from "axios";
axios.defaults.baseURL = "/api";
let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识
let cancelToken = axios.CancelToken;
let removeRepeatUrl = ever => {
for (let p in pending) {
// 判断是否存在重复请求
if (
pending[p].config &&
pending[p].config.url === ever.url &&
pending[p].config.method === ever.method
) {
if ((isObjectValueEqual(pending[p].config), ever))
//当当前请求在数组中存在时执行函数体
pending[p].cancle(); //执行取消操作
pending.splice(p, 1); //把这条记录从数组中移除
}
}
};
// 请求拦截器
axios.interceptors.request.use(
config => {
console.log("config", config);
//在一个ajax发送前执行一下取消操作
removeRepeatUrl({
method: config.method,
url: config.url,
params: config.params,
data: config.data
});
// 创建cancleToken和cancle取消请求方法, 每个请求都不一样的哦
config.cancelToken = new cancelToken(c => {
// 自定义唯一标识
pending.push({
config: {
method: config.method,
url: config.url,
params: config.params,
data: config.data
},
cancle: c //
});
});
return config;
},
err => {
return Promise.reject(err);
}
);
// 响应拦截器
axios.interceptors.response.use(
res => {
console.log("响应", res);
removeRepeatUrl({
method: res.config.method,
url: res.config.url,
params: res.config.params,
data: res.config.data
}); //在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
const data = res.data;
return data;
},
err => {
return Promise.reject(err);
}
);
export const request = config => {
return axios({
...config
});
};
/**
*比较两个对象是否相等
* @method isObjectValueEqual
* @param {Object} a 对象a
* @param {Object} b 对象b
* @return {Boolean}
*/
function isObjectValueEqual(a, b) {
console.log(a, b);
// 判断两个对象是否指向同一内存,指向同一内存返回true,同时比较null和undefined情况
if (a == b) return true;
if (a == null || a == undefined || b == null || b == undefined) {
return false;
}
// 获取两个对象键值数组
let aProps = Object.getOwnPropertyNames(a);
let bProps = Object.getOwnPropertyNames(b);
// 判断两个对象键值数组长度是否一致,不一致返回false
if (aProps.length !== bProps.length) return false;
// 遍历对象的键值
for (let prop in a) {
// 判断a的键值,在b中是否存在,不存在,返回false
if (b.hasOwnProperty(prop)) {
// 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false
if (typeof a[prop] === "object") {
if (!isObjectValueEqual(a[prop], b[prop])) return false;
} else if (a[prop] !== b[prop]) {
return false;
}
} else {
return false;
}
}
return true;
}
api方法
import { request } from "./setting1";
export function getList(params) {
return request({
url: "/list",
method: "get",
params: params
});
}
export function getList1(params) {
return request({
url: "/list1",
params: params,
method: "get"
});
}
vue文件调用:
<template>
<div>
<el-button type="primary"
@click="handleGO">请求1</el-button>
<el-button type="primary"
@click="handleGet">请求2</el-button>
</div>
</template>
<script>
import { getList, getList1 } from "@/api/index";
export default {
name: "WorkspaceJsonIndex",
data() {
return {};
},
mounted() {},
methods: {
handleGO() {
getList({
requestName: "11",
}).then((res) => {
console.log("请求成功", res);
});
},
handleGet() {
getList1().then((res) => {
console.log("请求成功11", res);
});
},
},
};
</script>
<style lang="scss" scoped>
</style>
实测:
可以看到我开启 slow 3G将网速变慢, 然后连续请求就会取消掉之前的啦
详细原理:
原理这里来源: Axios用cancelToken取消未完成的异步请求
这边方便查看,转载过来了,详细请跳转链接查看哈
整体思路是 axios请求在xhrAdapter
里新建了一个XHR连接,然后判断是否有cancleToken,如果有则通过config.cancelToken.promise.then
的处理,来达到终止XHR连接(abort)的目的。
作为一个Promise,要触发then,这个Promise必须resolve,而这个resolve
,则应该是可以由用户触发的,才能达到目的。而用户触发就是通过调用cancle函数触发。
由于每个axios.get/post
都会生成一个对应的XHR连接,所以,每个连接都需要由source()
工厂函数,生成新的token和对应的cancel,否则,使用相同的token,调用cancel时,会把几个连接一起停掉,这一点需要注意。
对于浏览器(区别于node),Axios创建的异步连接,本质上是新建了一个XMLHttpRequest
,然后包装Promise。
// Axios > lib > adapters > xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
// ...
var request = new XMLHttpRequest();
// ...
if (config.cancelToken) { // 如果有cancletoken
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();// 取消请求
reject(cancel);
// Clean up request
request = null;
});
}
// ...
// Send the request
request.send(requestData);
}
}
cancelToken.source
是个工厂函数,通过它,返回token实例和一个cancel处理函数。
// Axios > lib > cancel > cancelToken.js
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
可以看到,整体思路是在xhrAdapter
里新建了一个XHR连接,然后通过config.cancelToken.promise.then
的处理,来达到终止XHR连接(abort)的目的。
作为一个Promise,要触发then,这个Promise必须resolve,而这个resolve
,则应该是可以由用户触发的,才能达到目的。
CancelToken
这个构造函数,返回的实例,包含一个promise。
// Axios > lib > cancel > CancelToken.js
function CancelToken(executor) {
// ...
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
在source()
函数里,new CancelToken的时候,executor函数只有一个操作:cancel = c。
这里executor(function cancel(...) {...}))
,实际把整个函数赋值过去,即cancel = function cancel(...) {...})
。然后把这个cancel函数,连同CancelToken
实例,一起暴露给用户。即,必须在CancelToken
实例上执行对应的cancel函数,才会起效。
总结
所以梳理一下执行流程就是:
|cancel -> resolvePromise -> then -> request.abort()
由于每个axios.get/post都会生成一个对应的XHR连接,所以,每个连接都需要由source()工厂函数,生成新的token和对应的cancel,否则,使用相同的token,调用cancel时,会把几个连接一起停掉,这一点需要注意。
更多推荐
所有评论(0)