最近在做一个图片懒加载的需求,因为使用公司组件库的限制,没办法直接使用 data-src 属性加 Intersection Observer 来实现,也没法使用 getBoundingClientRect 方法判断图片的位置来实现,便采取了通过 requestAnimationFrame 的方式限制每秒钟加载图片的个数来实现。因为是内部工具,仅在公司内使用,因此可以不考虑网速等因素。基于此,想到了另外一个场景,即批量上传,同样需要限制每次上传的数量。
思路
使用 Promise.race 判断是否已达到并发的最大数量。依次遍历数据,将异步请求放入并发池中,当并发池中的任务数量达到最大值时,通过 Promise.race 等待并发池中的任务完成,每完成一个任务,就将该任务从并发池中移除,然后将下一个数据的请求任务放入并发池中,直到所有任务都执行完成。
代码实现
/**
* 异步任务执行最大并发为max的函数
* @param {*} max 最大并发数
* @param {*} data 要处理的数据
* @returns
*/
const concurrency = (max, data = []) => {
// 并发池
let pool = [];
return async function (fn) {
if (typeof fn !== "function") {
throw Error(`param ${fn} is expect a function.`);
}
for (let i = 0; i < data.length; i++) {
// 异步任务都采用Promise的形式,加convertToPromise包裹是处理执行的是非异步任务的情况
const task = convertToPromise(fn.call(this, data[i]));
pool.push(task);
task.then(() => {
const finishedTaskIndex = pool.findIndex((item) => item === task);
pool.splice(finishedTaskIndex, 1);
});
// 这一步判断是关键,当并发池占满时,要等待前面的任务执行完才能再往里面添加任务
if (pool.length === max) {
await Promise.race(pool);
}
}
// 等待并发池中所有的任务都执行完成
// 不加这行,会出现最后几个任务都加入并发池了,但是并没有执行完成,就被判断为已经全部执行完了
await Promise.all(pool);
console.log("任务执行完毕!");
};
};
function convertToPromise(task) {
if (typeof task?.then === "function") {
return task;
}
return new Promise((resolve) => {
resolve(task);
});
}
// 测试
const data = [1, 2, 3, 4, 5, 6, 7];
const executer = concurrency(5, data);
// 执行异步任务
const fn = function (param) {
return new Promise((resolve, reject) => {
// 随机延迟定时任务的执行
const time = Math.floor(Math.random() * 10);
setTimeout(() => {
console.log(param, "延迟: ", time);
resolve();
}, time * 1000);
});
};
// 执行同步任务
const f2 = function (params) {
console.log(params);
};