引言
这一系列文章记录了我多年工作中积累的心得体会,旨在与大家分享经验,欢迎各位大佬指导,共同提升技术水平。
承接上篇
前文(如何封装一个好用的网络请求(二)-- 错误统一处理)已经介绍了如何统一的处理错误的请求,目前就已经可以用于大型项目了,本篇将讨论如何进一步优化,具体来说,就是如何缓存请求结果并取消重复请求,以返回缓存的结果。
代码仓库在 案例一:网络封装 · Duck/EmpiricalCase - 码云 - 开源中国 (gitee.com),可以下载下来看完整代码
一、作用
重复请求优化有以下几个作用,可以根据项目需求来决定是否引入:
-
减轻服务器压力:假如有 10 个相同的请求,服务器需要开 10 个线程处理它们。实际上,只需要处理第一个请求即可,这样可以极大地减轻服务器的压力。
-
提高用户体验:用户只需要等待第一个请求的时间即可得到响应,显著提升体验。
二、适用场景
在 Vue 的组件开发中,例如将文章项写成一个组件,然后在列表中循环生成 10 个该组件,组件内部通过接口获取数据字段。如果没有取消重复请求的机制,就会同时发送 10 个请求。
在这种情况下,只需要等待第一个请求完成,其他请求复用第一个请求返回的结果即可。
三、思路
- 在请求前做一层拦截,将请求放入一个容器内,将请求的
路径 + 请求方式 + 请求参数
作为 key。 - 后续请求进入前检查是否存在相同的 key,如果有,代表有重复请求正在进行,此时等待。
- 第一个请求完成后,将其结果缓存起来,并分发给其他等待中的请求,然后删除该 key,完成请求。
我们提取关键词:拦截器代表请求前拦截器,完成后代表响应拦截器,还有一个存储等待请求的容器
四、代码
我们在 src/utils/request下面新建一个文件 repeatRequest.js
,实现重复请求相关的逻辑。
1. 工具函数:生成请求key
js
// 用于根据当前请求的信息,生成请求 Key;
function generateReqConfig(config) {
const { type, url, data } = config; // 请求方式,参数,请求地址,
if (data instanceof FormData) {
return;
}
const dataStr = JSON.stringify(data);
return [url, type, dataStr].join(',');
}
2. 设置请求中容器,并添加 get、set、delete方法
js
// 记录请求中的请求,url为key,axiosConfig作为值,请求后的值作为value
const pendingRequest = new Map();
/**
*添加进请求中的对象中
* @param {*} config
* @returns
*/
function addPendingRequest(config) {
const requestKey = generateReqConfig(config);
if (requestKey && !pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, null);
}
console.log('添加请求中请求', pendingRequest);
}
/**
* 设置请求中的对象,把成功请求放进去
*/
function setSuccessRequest(config, data) {
const requestKey = generateReqConfig(config);
pendingRequest.set(requestKey, data);
console.log('请求成功,设置数据', pendingRequest);
// 1s后,删除请求中数据,释放内存
setTimeout(() => {
deletePendingRequest(config, requestKey);
}, 500);
}
/**
* 删除请求中数据
*/
function deletePendingRequest(config, requestKey) {
if (!requestKey) {
requestKey = generateReqConfig(config);
}
console.log('删除数据', pendingRequest);
pendingRequest.delete(requestKey);
}
/**
* 获取成功的请求参数
* @param {*} config
* @param {*} requestKey
* @returns
*/
function getSuccessRequest(config, requestKey) {
if (!requestKey) {
requestKey = generateReqConfig(config);
}
console.log('获取数据', pendingRequest);
return pendingRequest.get(requestKey);
}
3. 调整请求函数
- 在请求前,添加进请求中容器,后续请求等待,设置定时器,每隔300ms获取一遍是否有结果返回
js
/**
* 请求拦截器
* @param {Object} config
* @return false 表示取消请求
*/
function requestInterceptor(config) {
// 添加进请求等待容器
addPendingRequest(config);
return config;
}
js
function ajaxFunc(config) {
const { url, type = 'get', data, headers } = config;
const realUrl = baseUrl + url;
return new Promise((resolve, reject) => {
// 判断重复请求
let timer = null;
const requestKey = generateReqConfig(config);
if (requestKey && pendingRequest.has(requestKey)) {
// 再次循环查询第一个请求的是否返回数据回来,返回有三种
// 1. undefined,标识没有这个请求中的请求了,那么代表第一个请求是错误的,直接返回报错回去
// 2. null,表示第一个请求还在请求中
// 3. 有值,表示第一个成功,直接服用数据
timer = setInterval(() => {
const data = getSuccessRequest(config, requestKey);
if (data === undefined) {
clearInterval(timer);
reject();
}
if (data) {
clearInterval(timer);
resolve(data);
}
}, 300);
return;
}
xxx之前写的逻辑xxxx
- 成功拦截器设置成功参数,错误拦截器删除key
js
success: async function (responseData) {
// 响应拦截器:请求成功
try {
const result = await responseInterceptorSuccess(responseData);
// 请求成功,设置请求中数据
setSuccessRequest(config, result);
resolve(result);
} catch (error) {
deletePendingRequest(config);
reject(error);
}
},
error: function (response) {
// 响应拦截器:请求失败
responseInterceptorError(response);
deletePendingRequest(config);
// 把响应数据返回出去,页面如果要处理,可以获取
reject(response.responseJSON);
},
全部代码
js
/**
* 执行AJAX请求
* @param {string} url 请求的URL
* @param {string} type 请求类型(如GET、POST等)
* @param {Object|string} data 要发送的数据
* @returns {Promise} 返回一个Promise对象,成功时resolve响应结果,失败时reject错误对象
*/
const baseUrl = 'http://localhost:8080';
function ajaxFunc(config) {
const { url, type = 'get', data, headers } = config;
const realUrl = baseUrl + url;
return new Promise((resolve, reject) => {
// 判断重复请求
let timer = null;
const requestKey = generateReqConfig(config);
if (requestKey && pendingRequest.has(requestKey)) {
// 再次循环查询第一个请求的是否返回数据回来,返回有三种
// 1. undefined,标识没有这个请求中的请求了,那么代表第一个请求是错误的,直接返回报错回去
// 2. null,表示第一个请求还在请求中
// 3. 有值,表示第一个成功,直接服用数据
timer = setInterval(() => {
const data = getSuccessRequest(config, requestKey);
if (data === undefined) {
clearInterval(timer);
reject();
}
if (data) {
clearInterval(timer);
resolve(data);
}
}, 300);
return;
}
// 请求拦截器
if (requestInterceptor(config) === false) {
return reject();
}
$.ajax({
url: realUrl,
type,
data,
success: async function (responseData) {
// 响应拦截器:请求成功
try {
const result = await responseInterceptorSuccess(responseData);
// 请求成功,设置请求中数据
setSuccessRequest(config, result);
resolve(result);
} catch (error) {
deletePendingRequest(config);
reject(error);
}
},
error: function (response) {
// 响应拦截器:请求失败
responseInterceptorError(response);
deletePendingRequest(config);
// 把响应数据返回出去,页面如果要处理,可以获取
reject(response.responseJSON);
},
});
});
}
/**
* 请求拦截器
* @param {Object} config
* @return false 表示取消请求
*/
function requestInterceptor(config) {
addPendingRequest(config);
return config;
}
/**
* 响应拦截器:请求成功,返回2xx的code
* @param {Object} response
*/
function responseInterceptorSuccess(responseData) {
// 判断请求是否一个文件流
if (typeof responseData !== 'object') {
return Promise.resolve(responseData);
}
// 成功
if (responseData.code === 0) {
return Promise.resolve(responseData);
}
// 逻辑报错
if (responseData.code === 1001) {
handle1002(responseData);
return Promise.reject(responseData);
}
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(responseData);
return Promise.reject(responseData);
}
/**
* 响应拦截器:请求失败,即返回除了2xx外的code
* @param {Object} response
*/
function responseInterceptorError(response) {
const httpCode = response.status;
const error = {
type: 'http',
code: httpCode,
message: '请求出错',
};
if (!window.navigator.onLine) {
// 网路错误
handleOffline(error);
return;
}
if (httpCode === 403) {
handle403(error);
return;
}
// 后面可以接上其他的处理逻辑
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(error);
}
五、前后效果
在这里,我用 for 循环发送 10 个请求,模拟上述适用场景。
js
async function a() {
const res = await ajaxFunc({
url: '/ok',
type: 'get',
data: {
name: 'zhangsan',
age: 18,
},
});
console.log('业务代码--请求成功返回:', res);
}
for (let i = 0; i < 10; i++) {
a();
}
这样一来,你就能有效地缓存请求结果并取消重复请求,优化网络请求的性能。希望这篇文章对你有所帮助