前言:
1、浏览器对同一域名下同一时间点的最大连接数做了限制,谷歌是6个,其他浏览器可百度查看相关资料。
2、浏览器同一时间点内发送的请求过多,会导致请求很慢页面卡顿的情况
解决问题
1、封装限制Promise异步任务并发请求数
核心函数也就两个。
调用器:就是把真正的执行函数和参数传入,创建返回一个新的Promise,而这个新Promise的什么时候返回,取决于这个异步任务何时被调度。Promise内部主要就是创建一个任务,判断任务是执行还是入队。
创建任务:实际上就是返回了一个函数,将真正的执行函数放在里面执行。这里利用了Promise的finally方法,在finally中判断是否执行下一个任务,实现任务队列连续消费的地方就是这里。
limitPromise.js
/**
* 封装axios并发请求数
*/
class LimitPromise {
constructor (max) {
// 异步任务“并发”上限
this._max = max || 6;
// 当前正在执行的任务数量
this._count = 0;
// 等待执行的任务队列
this._taskQueue = [];
}
/**
* 调用器,将异步任务函数和它的参数传入
* @param caller 异步任务函数,它必须是async函数或者返回Promise的函数
* @param args 异步任务函数的参数列表
* @returns {Promise<unknown>} 返回一个新的Promise
*/
call (caller, ...args) {
return new Promise((resolve, reject) => {
const task = this._createTask(caller, args, resolve, reject);
if (this._count >= this._max) {
// console.log('count >= max, push a task to queue', this._count , this._max, this._taskQueue)
this._taskQueue.push(task);
} else {
// console.log('数组中的对列长度还没超过6个', this._count)
task();
}
})
}
/**
* 创建一个任务
* @param caller 实际执行的函数
* @param args 执行函数的参数
* @param resolve
* @param reject
* @returns {Function} 返回一个任务函数
* @private
*/
_createTask (caller, args, resolve, reject) {
return () => {
// 实际上是在这里调用了异步任务,并将异步任务的返回(resolve和reject)抛给了上层
caller(...args)
.then(resolve)
.catch(reject)
.finally(() => {
// 任务队列的消费区,利用Promise的finally方法,在异步任务结束后,取出下一个任务执行
this._count--;
if (this._taskQueue.length) {
// console.log('a task run over, pop a task to run', this._taskQueue)
let task = this._taskQueue.shift();
task();
} else {
// console.log('task count = ', count)
}
})
this._count++;
// console.log('task run , task count = ', this._count)
}
}
}
export default LimitPromise;
2、在我们封装的axios文件使用并发数限制
limitRequest.js
import axios from 'axios';
import { message } from 'antd';
import qs from 'qs';
// 引入上面封装好的并发数限制文件
import LimitPromise from './limitPromise';
// 并发请求上限
const MAX = 5;
// 调用核心控制器类
const limitP = new LimitPromise(MAX);
//响应时间
axios.defaults.timeout = 300000; // 5 min
axios.defaults.headers.post['Content-Type'] = 'application/json';
// request interceptor
axios.interceptors.request.use(
config => {
config.headers['Content-Type'] = 'application/json';
config.headers['datae-token'] = localStorage.getItem('xyToken');
//application/x-www-form-urlencoded
if (config.resType === 'form') {
config.headers['Content-Type'] =
'application/x-www-form-urlencoded';
config.data = qs.stringify(config.data);
return config;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// response interceptor
axios.interceptors.response.use(
response => {
if (response.status >= 400) {
// 没登录/过期
if (response.status === 401) {
message.error('登录态过期,请重新登录');
// ...写入自己的代码逻辑和判断
return;
}
return Promise.reject(response);
}
return response;
},
error => {
if (error.response.status === 401) {
message.error('登录态过期,请重新登录');
// ...写入自己的代码逻辑和判断
} else {
message.error('网络连接超时');
}
return Promise.reject(error);
}
);
// 导出axios的几种请求方式
export const getP = (url, params, config = {}) => {
return limitP.call(
axios.get,
url,
{
params: params,
...config,
}
).then(res => res.data);
};
export const postP = (url, params, config = {}) => {
return limitP.call(
axios.post,
url,
params,
{
...config,
}
).then(res => res.data);
};
// 如果还需要其他请求,请结合aixos的api进行相关配置
export default axios;
3、统一封装的api的请求路径
api.js
import { getP, postP } from './tools/axios/limitRequest';
import {
sysPrefix,
} from './configs';
export default {
// 首页(中控台)这也页面下的所有接口都是加载页面就进行请求的,可见这里的请求数很多
home: {
// 监控预警————等级xxxxx
xxxxx: () => getP(`${sysPrefix}/warningInfo/xxxxx`),
// 监控预警————xxxxxx
xxxx: (params) => getP(`${sysPrefix}/warningInfo/xxxx`, params),
// 监控预警————确认
xxxx: (params) => postP(`${sysPrefix}/warningInfo/xxxx`, params),
// 监控预警————清除
xxx: (params) => postP(`${sysPrefix}/warningInfo/xxxx`, params),
// 作业异常单————xxxxxxTab数据
xxxxxx: (params) => getP(`${sysPrefix}/planResult/xxxx`, params),
// 作业异常单————获取表格数据
xxxx: (params) => postP(`${sysPrefix}/planResult/xxxxx`, params),
// 作业异常单————表格中的处理按钮
xxx: (params) => postP(`${sysPrefix}/planResult/xxxx`, params),
// 我的导航————查询回显导航
xxxxx: params => postP(`${sysPrefix}/system/navigation/xxxx`, params),
// 我的导航————添加快捷入口导航
xxxxxx: params => postP(`${sysPrefix}/system/navigation/xxxxx`, params),
// 我的导航————更新最近访问导航
xxxxxx: params => postP(`${sysPrefix}/system/navigation/xxxxx`, params),
// Hadoop磁盘修复————请求表格数据
xxxxx: (params) => postP(`${cdhnm}/diskFailureRecovery/xxxxx`, params),
// Hadoop磁盘修复————xxxxxxx
xxxx: (params) => postP(`${sysPrefix}/diskFailureRecovery/xxxx`, params),
// Hadoop磁盘修复————xxxxxxx
xxxxx: (params) => postP(`${sysPrefix}/diskFailureRecovery/xxxxx`, params),
},
};
4、在组件页面中调用上面封装好的api
home.js
import api from 'src/api';
// 下面只是简约写出请求部分的代码
useEffect(() => {
api.home
.xxxx().then(res => {
console.log(res);
}).catch((err) => {
console.log(err);
});
// ......还有很多ajax请求
}, []);
这样像在home.js中页面dom加载完成后一次性请求很多个接口的时候就会根据上面做的并发数限制进行有效的控制了。
推荐一篇好文:
JavaScript 中如何实现并发控制?
参考资料:https://www.jianshu.com/p/cc706239c7ef