并行限制的Promise
知耻而后勇
# 类
JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个。完善下面代码的 Scheduler 类,使以下程序能够正常输出:
class Scheduler {
constructor() {
this.queue = [];
this.maxCount = 2;
this.runCounts = 0;
}
add(promiseCreator) {
this.queue.push(promiseCreator);
}
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request();
}
}
request() {
if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
return;
}
this.runCounts++;
this.queue
.shift()()
.then(() => {
this.runCounts--;
this.request();
});
}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// output: 2 3 1 4
可以看到,最多时存在两个并行的 Promise,并且一个 Promise 执行完成之后,执行新的 Promise,并且新执行的 Promise 不会影响到另一个正在执行的 Promise。
既然如此的话,就不能使用 Promise.all()和 Promise.race()这两个 API 了,Promise.all()会等待所有 Promise 完成,Promise.race()只会执行一个 Promise。
其实从 Promise 依序进行执行,可以使用队列先进先出的特性,add 操作知识每次用队列中插入 Promise Creator,判断当前执行数量是否小于 2,如果小于 2 就从队列中弹出 Promise Creator 执行并给执行的 Promise 绑定 then 函数,then 函数被调用就说明当前 Promise 已经执行完成,重复当前操作,可以看出是一个递归
的操作。
# 函数
function limitRequest(limit, requestes) {
let runCount = 0;
const request = () => {
if (!requestes || !requestes.length || runCount >= limit) {
return;
}
runCount++;
requestes
.shift()()
.then(() => {
runCount--;
request();
});
};
for (let i = 0; i < limit; i++) {
request();
}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const requestes = [
() => timeout(1000).then(() => console.log(11)),
() => timeout(500).then(() => console.log(22)),
() => timeout(300).then(() => console.log(33)),
() => timeout(400).then(() => console.log(44)),
];
limitRequest(2, requestes);
# 模拟请求
当我们需要保证代码在多个异步操作都完成后执行,通常我们会使用 Promise.all 来实现。以请求多张图片为例:
// 为了演示方便,我们在此用fetchImage函数来模拟异步请求图片,返回成功提示
function fetchImage(url) {
// 模拟请求的响应时间在0 - 1s之间随机
const timeCost = Math.random() * 1000;
return new Promise((resolve) => setTimeout(resolve, timeCost, "get: " + url));
}
// 待请求的图片
const imageUrls = [
"pic_1.png",
"pic_2.png",
"pic_3.png",
"pic_4.png",
"pic_5.png",
"pic_6.png",
];
Promise.all(imageUrls.map((url) => fetchImage(url))).then((resList) =>
console.log(resList)
);
如果我们对并行的请求数量有限制,Promise.all 自身是不具有这个功能的。 那么接下来,我们依旧以上述的 fetchImage 为例,来实现一个可以对图片请求进行并行限制的函数:
/**
* @description 带并发限制的图片并发请求
* @param {Array} imageUrls 待请求的图片url列表
* @param {Object} limit 最大并发个数限制
* @return { Promise<Array> } resList
*/
function fetchImageWithLimit(imageUrls, limit = 4) {}
# promise.all
- 初始化 limit 个 Promise 对象,作为 Promise.all 的参数
- 每个 Promise 对象去 imageUrls 中取出一个 url 进行请求,若无则 resolve
- 每个 Promise 对象在当前请求成功后重复步骤 2
/**
* @description 带并发限制的图片并发请求
* @param {Array} imageUrls 待请求的图片url列表
* @param {Object} limit 最大并发个数限制
* @return { Promise<Array> } resList
*/
function fetchImageWithLimit(imageUrls, limit = 2) {
let urls = [...imageUrls];
let rs = new Map();
function run() {
if (urls.length > 0) {
const url = urls.shift();
console.log(url, " [start at] ", new Date().getTime() % 10000);
return fetchImage(url).then((res) => {
console.log(url, " [end at] ", new Date().getTime() % 10000);
rs.set(url, res);
return run();
});
}
}
const promiseList = Array(Math.min(limit, imageUrls.length))
.fill(Promise.resolve())
.map((promise) => promise.then(run));
return Promise.all(promiseList).then(() =>
imageUrls.map((item) => rs.get(item))
);
}
fetchImageWithLimit(imageUrls).then((res) => console.log(res));
# 参考链接
编辑 (opens new window)
上次更新: 2024/11/29, 10:10:04