重复请求取消(不发请求)
重复请求取消(不发请求)
axios 有自带的取消请求方式, 但是 在请求已发出时,取消只是状态取消, 其实请求已经发出,消耗了服务器资源(方式1)。本文探究的是 在调用请求A时, 在A未响应之前,所有的A重复请求都应该不发出,待A响应之后,将响应结果给到之前的所有A请求!(方式2,3)
基础代码
// request.js
export const request = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
// api/user.js 获取数据函数, request 就是axios实例
export function getCrypto1(params) {
return request({
url: "/echo",
method: "post",
params,
encryption: false,
hideMsg: true,
notSign: true,
notToken: true
});
}
// App.vue created
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
for (let i = 0; i < 5; i++) {
console.log("for: ", i);
// await sleep(500);
getCrypto1({ a: 11111 }).then((res) => {
console.log("res--P: ", res);
}).catch((err) => {
console.log("res--E: ", err);
});
}
#1 axios自带的取消请求(AbortController)
完整代码(request.js):
// 用于存储请求标识和对应的 AbortController
const pendingRequests = new Map();
// 请求拦截器
service.interceptors.request.use(
(config) => {
cancelRequestHandler(config);
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
cancelRequestHandler(response.config, "repsonse");
},
(error) => {
if (error.config) {
cancelRequestHandler(error.config, "repsonse");
}
if (error.name === "CanceledError") {
return;
}
return Promise.reject(error.response);
}
);
// 生成唯一请求标识的方法
function generateRequestKey(config) {
const { method, url, params, data } = config;
return `${method}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`;
}
// axios AbortController取消请求
function cancelRequestHandler(config, requestType = "request") {
// console.log("--cancel: ", config, requestType);
// 创建请求标识
const requestKey = generateRequestKey(config);
if (requestType === "request") {
// 如果已存在相同请求,取消之前的请求
if (pendingRequests.has(requestKey)) {
const controller = pendingRequests.get(requestKey);
controller.abort();
pendingRequests.delete(requestKey);
}
// 为当前请求创建新的 AbortController
const controller = new AbortController();
config.signal = controller.signal;
// 存储当前请求的控制器
pendingRequests.set(requestKey, controller);
} else {
pendingRequests.delete(requestKey);
}
}
#2 不发请求(axios)
大概思路就是 将重复请求在请求拦截器里用
return Promise.reject
直接进入响应拦截器错误里,在将结果返回即可
完整代码(request.js):
export const request = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
// 用于存储请求标识
const pendingRequests = new Map();
// 请求拦截器配置
request.interceptors.request.use(
(config) => {
const { shouldIntercept, promise } = handleDuplicateRequest(config);
if (shouldIntercept) {
return promise; // 如果是重复请求,返回挂起的 Promise
}
// 其它逻辑...
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
(response) => {
resolvePendingQueue(response);
// 其它逻辑...
return Promise.resolve(decrypts);
},
(error) => {
console.log("resp error: ", error);
if (error.cachedResponse) {
if (error.cachedResponse.status === 200) {
return Promise.resolve(error.cachedResponse?.data);
} else {
return Promise.reject(error.cachedResponse?.data);
}
}
if (error.config) {
rejectPendingQueue(error.response);
}
// 其它逻辑...
return Promise.reject(error.response);
}
);
// 生成唯一请求标识的方法
function generateRequestKey(config) {
const { method, url, params, data } = config;
return `${method}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`;
}
// 重复请求缓存
function handleDuplicateRequest(config) {
const requestKey = generateRequestKey(config);
if (pendingRequests.has(requestKey)) {
return {
shouldIntercept: true,
promise: new Promise((resolve, reject) => {
pendingRequests.get(requestKey).push({ resolve, reject });
})
};
}
// 如果是首次请求,初始化队列
pendingRequests.set(requestKey, []);
return { shouldIntercept: false, promise: null };
}
// 处理成功响应队列
function resolvePendingQueue(response) {
const requestKey = generateRequestKey(response.config);
const pendingQueue = pendingRequests.get(requestKey) || [];
pendingQueue.forEach(({ reject }) => reject({ cachedResponse: response }));
pendingRequests.delete(requestKey);
}
// 处理失败响应队列
function rejectPendingQueue(error) {
const requestKey = generateRequestKey(error.config);
const pendingQueue = pendingRequests.get(requestKey) || [];
pendingQueue.forEach(({ reject }) => reject({ cachedResponse: error }));
pendingRequests.delete(requestKey);
}
示例((成功):
示例(失败):
以上只发了一次请求
#3 不发请求(推荐,适合所有请求库)
基本思路: 对于第一个请求, 将promise存起来, 在该请求响应之前, 所有的相同请求都公用一个promise, 当第一个请求响应时,所有的promise都释放了,所有阻塞的相同请求都能拿到相同的结果!
完整代码(request.js)
export const request = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
// 创建缓存请求
export function createCacheedRequest(config) {
// 根据请求的 URL 和参数生成唯一的请求 key
const requestKey = generateRequestKey(config);
// 判断请求是否已经在进行中
if (!pendingRequests.has(requestKey)) {
// 如果没有该请求,发起请求并缓存该请求的 Promise
const requestPromise = service(config).finally(() => {
// 请求完成后,从 pendingRequests 中删除该请求
pendingRequests.delete(requestKey);
})
// 缓存该请求的 Promise
pendingRequests.set(requestKey, requestPromise);
}
// 返回正在进行的请求 Promise
return pendingRequests.get(requestKey);
}
// 不用再拦截器添加代码
// 调用的方式需要改一下
// 获取数据
export function getCrypto1(params) {
return createCacheedRequest({
url: "/echo",
method: "post",
params,
encryption: false,
hideMsg: true,
notSign: true,
notToken: true
});
}
当打开睡眠时间时
for (let i = 0; i < 5; i++) {
await sleep(500); // 每过500ms发出一个请求
console.log("for: ", i);
getCrypto1({ a: 1111 }).then((res) => {
console.log("res--P: ", res);
}).catch((err) => {
console.log("res--E: ", err);
});
// this.getCryptoData();
}
// node服务
// 接口耗时1500ms
app.post('/echo', (req, res) => {
console.log('received : ', req.body)
const data = req.body;
setTimeout(() => {
res.json({ code: 0, msg: 'success', data: 22222 });
// res.status(500).json({ code: -1, msg: 'error', data: null });
}, 1500)
});
这样就会第1个请求结束后,和第2,3,4 一起返回, 第五个请求重新发,不会被取消,单独返回。总共发出了两次请求