const privateWeakMap = new WeakMap();
function getConcurrent(_this) {
return privateWeakMap.get(_this)
}
class ConcurrentInterceptors {
/**
* 最大并发数量
* @param maxProgressCount
* @typeof number
*/
constructor(maxProgressCount) {
privateWeakMap.set(this,
{
// 最大并发数量
_maxProgressCount: maxProgressCount,
// 进行中的任务数量
_inProgressCount: 0,
// 等待中的任务
_waitingArr: [],
}
);
}
/**
* 拦截器
* @return Promise.resolve()
*/
interceptors = () => {
const { _inProgressCount, _maxProgressCount } = getConcurrent(this)
return new Promise(resolve => {
// 非数字、isNaN、不大于0 不拦截
if (typeof _maxProgressCount !== 'number' || isNaN(_maxProgressCount) || _maxProgressCount <= 0) {
resolve()
return
}
// 当前进行的任务数量是否大于最大并发数量
if (_inProgressCount < _maxProgressCount) {
// 未超出最大并发数量
getConcurrent(this)._inProgressCount += 1
resolve()
} else {
// 超出最大并发数量
getConcurrent(this)._waitingArr.push(resolve);
}
});
}
/**
* 启动下一个被拦截的任务
*/
next = () => {
// 当前进行中的数量减一
getConcurrent(this)._inProgressCount -= 1
// 等待发送的请求的移除一个
const nextResolve = getConcurrent(this)._waitingArr.shift();
if (nextResolve) {
// 要进行下一个任务了 当前进行中的数量加一
getConcurrent(this)._inProgressCount += 1
nextResolve();
}
}
/**
* 更新最大并发数量
* @param newMaxProgressCount
*/
updateMaxProgressCount = (newMaxProgressCount) => {
getConcurrent(this)._maxProgressCount = newMaxProgressCount;
}
/**
* 获取_maxProgressCount属性值
*/
get maxProgressCount() {
return getConcurrent(this)._maxProgressCount
}
set maxProgressCount(newVal) {
this.updateMaxProgressCount(newVal)
}
/**
* 获取_sendAxiosIngCount属性值
*/
get inProgressCount() {
return getConcurrent(this)._inProgressCount
}
set inProgressCount(newVal) {
throw new Error(`inProgressCount为只读属性`);
}
/**
* 获取_awaitSendAxiosArr属性值
*/
get waitingArr() {
return getConcurrent(this)._waitingArr
}
set waitingArr(newVal) {
throw new Error(`waitingArr为只读属性`);
}
}
export default ConcurrentInterceptors
AI代码解释:
这段代码定义了一个 ConcurrentInterceptors 类,用于管理并发任务的拦截。其核心目的是控制最大并发任务数,并处理超出最大并发数的任务。下面是对代码的详细解释:
1. privateWeakMap 和 getConcurrent 函数
const privateWeakMap = new WeakMap();
function getConcurrent(_this) {
return privateWeakMap.get(_this)
}
privateWeakMap 是一个 WeakMap 对象,用于存储 ConcurrentInterceptors 实例的私有数据。
getConcurrent 函数用于从 privateWeakMap 中获取指定实例 _this 的私有数据。
2. ConcurrentInterceptors 类
构造函数
constructor(maxProgressCount) {
privateWeakMap.set(this,
{
_maxProgressCount: maxProgressCount,
_inProgressCount: 0,
_waitingArr: [],
}
);
}
构造函数接收一个参数 maxProgressCount,表示最大并发任务数。
将实例的私有数据(最大并发数、进行中的任务数和等待中的任务数组)存储到 privateWeakMap 中。
interceptors 方法
interceptors = () => {
const { _inProgressCount, _maxProgressCount } = getConcurrent(this);
return new Promise(resolve => {
if (typeof _maxProgressCount !== 'number' || isNaN(_maxProgressCount) || _maxProgressCount <= 0) {
resolve();
return;
}
if (_inProgressCount < _maxProgressCount) {
getConcurrent(this)._inProgressCount += 1;
resolve();
} else {
getConcurrent(this)._waitingArr.push(resolve);
}
});
}
interceptors 方法返回一个 Promise。
方法检查 maxProgressCount 是否有效(是正数)。
如果当前进行中的任务数量小于最大并发数,则增加进行中的任务数并解析 Promise。
否则,将 resolve 函数推送到等待任务数组中,等待直到可以处理。
next 方法
next = () => {
getConcurrent(this)._inProgressCount -= 1;
const nextResolve = getConcurrent(this)._waitingArr.shift();
if (nextResolve) {
getConcurrent(this)._inProgressCount += 1;
nextResolve();
}
}
next 方法在任务完成时调用。
减少进行中的任务数,并从等待数组中取出一个 resolve 函数来处理下一个任务。
updateMaxProgressCount 方法
updateMaxProgressCount = (newMaxProgressCount) => {
getConcurrent(this)._maxProgressCount = newMaxProgressCount;
}
更新最大并发任务数的值。
maxProgressCount 属性
get maxProgressCount() {
return getConcurrent(this)._maxProgressCount;
}
set maxProgressCount(newVal) {
this.updateMaxProgressCount(newVal);
}
maxProgressCount 是一个 getter 和 setter。
获取 maxProgressCount 属性的值。
通过 setter 更新最大并发任务数。
inProgressCount 属性
get inProgressCount() {
return getConcurrent(this)._inProgressCount;
}
set inProgressCount(newVal) {
throw new Error(`inProgressCount为只读属性`);
}
inProgressCount 是一个 getter 和 setter。
获取进行中的任务数。
setter 抛出错误,因为 inProgressCount 是只读的。
waitingArr 属性
get waitingArr() {
return getConcurrent(this)._waitingArr;
}
set waitingArr(newVal) {
throw new Error(`waitingArr为只读属性`);
}
waitingArr 是一个 getter 和 setter。
获取等待中的任务数组。
setter 抛出错误,因为 waitingArr 是只读的。
总结
ConcurrentInterceptors 类用于管理任务的并发执行。它通过 interceptors 方法来处理任务,根据当前的并发任务数决定是否执行新任务或将其放入等待队列。通过 next 方法来启动等待队列中的任务。该类还提供了更新最大并发数的功能,并通过只读属性提供当前并发任务数和等待队列的访问。
// http 封装
"use strict";
import axios from "axios"
import bus from '@/utils/bus.js'
import store from "@/store"
import router from '@/router'
// 并发控制器
import ConcurrentInterceptors from './concurrentInterceptors';
const concurrent = new ConcurrentInterceptors(4) // 最大并发量
import {
getToken,
getRefreshExpires,
getRefreshToken
} from '@/utils/auth'
import {
refreshToken
} from 'api/api.js'
import { sysAppId } from "@/settings.js"
// import expiresCalculation from "./expires"
axios.interceptors.request.use(
async config => {
// config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
//=========================================================
// 请求拦截
const isToken = (config.headers || {}).isToken === false;
let token = getToken() ? getToken() : "";
if (token && !isToken) {
config.headers["Authorization"] = "Bearer " + token
config.headers["sysappid"] = sysAppId
}
await concurrent.interceptors() //并发配置
return config;
},
error => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
//响应拦截
// 当队列中有请求时,执行下一个请求
concurrent.next() //并发配置
return response;
},
error => {
concurrent.next() //并发配置
return Promise.resolve(error.response);
}
);
const whiteList = [
"/api/Jwt/token",
"/api/Jwt/AccessTokenByOracleJwtToken"
]
function getData(val, url) {
//处理请求数据
// 判断登录用户信息/权限
if (whiteList.join(",").indexOf(url) >= 0) {
// 密码登录:登录 手机登录:获取验证码、登录
return val
} else {
const token = getToken() ? getToken() : ""
if (!token) {
bus.$message.warning('用户令牌失效,请重新登录~')
setTimeout(async ()=>{
await store.dispatch('user/logout')
router.push('/login')
},100)
}
}
if (typeof val == "string") {
return JSON.parse(val)
} else {
return val
}
}
// 请求参数
function getOptions(url, method, data, params, queryAndBody,otherUrl) {
const options = {
method: method,
baseURL: otherUrl ? otherUrl : window.g.dev && '/' || window.g[`API_ROOT` + (location.search.split('systemcode=')[1] ? ('_' + location.search.split('systemcode=')[1].split('&')[0]) : '')],
url: url,
timeout: window.g.AXIOS_TIMEOUT ? window.g.AXIOS_TIMEOUT : 10000,
headers: {}
};
if(data && data.url){
delete data.url
}
if (!!queryAndBody) {
const paramsData = data;
const bodyObject = paramsData.bodyObject;
delete paramsData.bodyObject;
options.headers['Content-Type'] = 'application/json;charset=UTF-8';
options.params = getData(paramsData, url);
options.data = getData(bodyObject, url);
} else {
const pd = !params ? "data" : "params";
options[pd] = getData(data, url);
}
return options;
}
function getOptionsJwt(url, method, data, params, queryAndBody) {
const options = {
method: method,
baseURL: window.g.API_ROOT_JWT,
url: url,
timeout: window.g.AXIOS_TIMEOUT ? window.g.AXIOS_TIMEOUT : 10000,
headers: {}
};
if (!!queryAndBody) {
const paramsData = data;
const bodyObject = paramsData.bodyObject;
delete paramsData.bodyObject;
options.headers['Content-Type'] = 'application/json;charset=UTF-8';
options.params = getData(paramsData, url);
options.data = getData(bodyObject, url);
} else {
const pd = !params ? "data" : "params";
options[pd] = getData(data, url);
}
return options;
}
async function checkStatus(response) {
//处理响应数据
if (response && response.status === 200) {
// loading,如果http状态码正常,则直接返回数据
let res = response.data;
if (!res) {
bus.$message.error("服务器开了个小差");
return false;
}
if (res.code == 10000 || res.Code == 10000 || res.code == 10001 || res.StatusCode === 200 || res.StatusCode === 400 || res.StatusCode === 500) {
// bus.$message.error(res.message);
return res;
}else if(res.StatusCode === 400 || res.StatusCode === 500){
if(res.message){
bus.$message.error(res.message);
}
if(res.Msg){
bus.$message.error(res.Msg);
}
return res;
} else {
if((res.StatusCode == 403 || res.Code == 403 || res.code == 10050) && (res.message === '该令牌已失效' || res.Message === '令牌过期' || res.Msg === '令牌过期')){
console.log(res.Code == 403, res.Message == '令牌过期')
const refreshTimeFlag = calcRefreshTime()
if (refreshTimeFlag) {
// 用刷新令牌刷新访问令牌并重新发起请求
const _res = await handleRefreshToken(response)
return _res
} else {
bus.$message.warning(`${res.message || res.Message || res.Msg || '令牌过期'},请重新登录~`)
setTimeout(async () => {
await store.dispatch('user/logout')
router.push('/login')
}, 100)
}
return response
} else {
bus.$message.warning(`${res.message || res.Message || res.Msg || '令牌过期'},请重新登录~`)
}
return false;
}
} else if (response.status === 401) {
const refreshTimeFlag = calcRefreshTime()
if (refreshTimeFlag) {
// 用刷新令牌刷新访问令牌并重新发起请求
const _res = await handleRefreshToken(response)
return _res
} else {
showMessage('warning',`${response.data.message || response.data.Message || response.data.Msg || '令牌过期'},请重新登录~`)
setTimeout(async () => {
await store.dispatch('user/logout')
router.push('/login')
}, 100)
}
return response
} else {
if(response.data.code == 10040 && response.data.message == '该令牌已失效'){
bus.$message.error(response.data.message+'~ 请重新登录!');
setTimeout(async ()=>{
await store.dispatch('user/logout')
router.push('/login')
}, 100)
}if(response.data.code == 403 && response.data.message == '该令牌已失效' || response.data.message == 'Unauthorized'){
bus.$message.error(response.data.message+'~ 请重新登录!');
setTimeout(async ()=>{
await store.dispatch('user/logout')
router.push('/login')
}, 100)
}else if (response.data.Message) {
bus.$message.error(response.data.Message || response.data.Msg);
} else if (response.data.message) {
bus.$message.error(response.data.message || response.data.Msg);
}
return false;
}
}
// 计算判断刷新令牌是否过期
function calcRefreshTime() {
const refreshTime = getRefreshExpires()
const nTime = new Date().getTime()
const nTimeSecond = Math.floor(nTime / 1000)
const expTime = refreshTime - nTimeSecond
if (expTime < 10) {
return false
}
return true
}
export async function handleRefreshToken(response) {
// 如果能走到这一步,说明客户端时间已经判断过,再判断下token
if (response.data.code === 10050) {
// 如果有这个字段 去刷新token
let token = getToken()
let refreshTokenStr = getRefreshToken() // 刷新令牌
// console.log('handlerefreshtoken token', token)
if (!token || !refreshTokenStr || whiteList.indexOf(response.config.url)>=0) {
// 如果token不存在
await store.dispatch('user/logout')
router.push('/login')
} else {
let data = {
accessToken: token,
refreshToken: refreshTokenStr
}
return new Promise(async (resolve, reject) => {
await refreshToken(data).then(async (res) => {
if (res) {
const cont = res.content
await store.dispatch('user/setNewTokenInfo', cont)
// 重新调用接口
resolve(await repeatRequest(response))
} else {
console.log('token刷新失败')
reject()
}
}).catch(error => {
reject(error)
})
})
}
} else {
console.log('客户端时间未过期,token过期且无10050')
// 如果token过期
bus.$message.warning('用户令牌失效,请重新登录~')
await store.dispatch('user/logout')
router.push('/login')
return false
}
}
// 令牌时间过期后的刷新令牌方法
export function postRefreshToken() {
// 如果有这个字段 去刷新token
let token = getToken()
let refreshTokenStr = getRefreshToken() // 刷新令牌
return new Promise(async (resolve) => {
if (!token || !refreshTokenStr) {
// 如果token不存在
resolve(false)
} else {
let data = {
accessToken: token,
refreshToken: refreshTokenStr
}
await refreshToken(data).then(async (res) => {
if (res) {
const cont = res.content
await store.dispatch('user/setNewTokenInfo', cont)
// 重新调用接口
resolve(true)
} else {
resolve(false)
}
}).catch(error => {
resolve(false)
})
}
})
}
function checkCode(error) {
bus.$message.error("网络异常~");
}
export default {
async post(url, data, query, queryAndBody,otherUrl) {
// await expiresCalculation()
// .then(() => { })
// .catch(() => {
// })
const options = getOptions(url, "post", data, query, queryAndBody,otherUrl)
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async get(url, data, query,otherUrl) {
const options = getOptions(url, "get", data, query,false,otherUrl);
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async put(url, data, query, queryAndBody,otherUrl) {
const options = getOptions(url, "put", data, query, queryAndBody,otherUrl);
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async delete(url, data,query,queryAndBody,otherUrl) {
const options = getOptions(url, "delete", data, query,queryAndBody,otherUrl);
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async patch(url, data, query,otherUrl) {
const options = getOptions(url, "patch", data, query,'',otherUrl);
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
// JWT
async postJwt(url, data, query, queryAndBody) {
const options = getOptionsJwt(url, "post", data, query, queryAndBody)
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async getJwt(url, data, query, queryAndBody) {
const options = getOptionsJwt(url, "get", data, query, queryAndBody)
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async putJwt(url, data, query, queryAndBody) {
const options = getOptionsJwt(url, "put", data, query, queryAndBody)
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
async deleteJwt(url, data, query) {
const options = getOptionsJwt(url, "delete", data, query)
return axios(options)
.then(response => {
return checkStatus(response);
})
.catch(res => {
return checkCode(res);
});
},
// 下载
async getFile(url, data, query) {
let options = getOptions(url, "get", data, query);
options.responseType = 'blob'
return axios(options)
.then(response => {
return response
})
.catch(res => {
return res;
});
},
}
参考:面试官:假如有几十个请求,如何去控制并发?_w_omit的技术博客_51CTO博客