前言
在一些场景中,我们会遇到高频率大批量请求数据,密集型 CPU 运算。为了避免请求过于频繁导致资源不足情况。 需要保持并行请求的数量固定。
方案
- 基于Promise.race的特性配合Promise.all 实现并行请求的限制请求数量,保持请求数量。
- 基于队列先进先出的特性配合Promise 构成微任务异步队列,实现并行请求的限制请求数量,保持请求数量。
基于Promise.race配合Promise.all 实现异步并发限制
运用js的事件循环机制 ,进行异步处理,以下是思路步骤:
- 创建异步函数,接受三个参数(限制数量、数据数组、处理函数);
- 函数内部执行处理逻辑;
- 初始化结果数组、运行执行数组变量 ;
- 循环数据数组,包裹处理函数为
Promise对象
; - 添加
Promise对象
到结果数组(之后的Promise对象
执行结果仍保存在结果数组中); - 判断数据数组的长度是否小于等于限制数量;
- 如果是继续限量执行;
- 在当前的
Promise对象
其后添加删除自身的处理逻辑(清除执行数组); - 将当前的
Promise对象
添加到执行数组中; - 判断执行数组长度是否大于等于限制数量;
- 为真,则运行
Promise.race(执行数组)
,进入微任务队列; - 为假,跳过;
- 为真,则运行
- 不是则跳过;
- 使用
Promise.all
全量执行结果数组并返回结果; - 返回运行结果;
实现代码:
const asyncPool = async (poolLimit, array, iteratorFn) => {
const resultList = [];
const executing = [];
for (const item of array) {
console.log("循环", item);
const p = Promise.resolve().then(() => {
console.log("初始化", item);
return iteratorFn(item, array);
});
resultList.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => {
return executing.splice(executing.indexOf(e), 1);
});
executing.push(e);
if (executing.length >= poolLimit) {
console.log("运行Promise.race");
await Promise.race(executing);
}
}
}
return Promise.all(resultList);
};
示例:
const timeout = (i) =>
new Promise( (resolve) => {
setTimeout(resolve, i, i)
});
const main = async () => {
const aa = await asyncPool(
3,
[10, 20, 30, 40, 50, 60, 60, 70, 80, 1000,],
timeout
);
console.log("aa=>", aa);
};
main();
运行结果:
如运行结果所示,函数运行,一直保持在限制运行数量为3。
基于队列配合Promise实现异步并发限制
基本的逻辑是,有一个任务池,通过初始化任务池,设置异步并发的数量。通过向任务池添加异步任务,利用Promise.all
全部执行完再返回结果的特性,将异步任务收集起来组合成数组传给Promise.all
。执行函数并返回结果。
流程图如下:
实现代码:
// 队列
class Queue {
constructor() {
this._queue = [];
}
push(value) {
return this._queue.push(value);
}
shift() {
// TODO 优化出队操作 shift 操作的时间复杂度为 O(n)。使用 reverse + pop 的方式,引入双数组的设计,减低时间复杂度类O(1)
return this._queue.shift();
}
isEmpty() {
return this._queue.length === 0;
}
}
// 延迟任务
class DelayedTask {
constructor(resolve, fn, args) {
this.resolve = resolve;
this.fn = fn;
this.args = args;
}
}
// 任务池
class TaskPool {
constructor(size) {
this.size = size;
this.queue = new Queue();
}
addTask(fn) {
return (...args) => {
return new Promise((resolve) => {
this.queue.push(new DelayedTask(resolve, fn, args));
if (this.size) {
this.size--;
const {
resolve: taskResolve,
fn: taskFn,
args: taskArgs,
} = this.queue.shift();
taskResolve(this.runTask(taskFn, taskArgs));
}
});
};
}
pullTask() {
if (this.queue.isEmpty()) {
return;
}
if (this.size === 0) {
return;
}
this.size--;
const { resolve, fn, args } = this.queue.shift();
resolve(this.runTask(fn, args));
}
runTask(fn, args) {
const result = Promise.resolve(fn(...args));
result.finally(() => {
this.size++;
this.pullTask();
});
return result;
}
}
示例:
const cc = new TaskPool(4);
const taskList = [1000, 3000, 200, 1300, 800, 2000];
const task = (timeout) =>
new Promise((resolve) =>
setTimeout(() => {
resolve(timeout);
}, timeout)
);
async function startConcurrentControl() {
console.time("xxx");
await Promise.all(taskList.map(cc.addTask(task)));
console.timeEnd("xxx");
}
startConcurrentControl();