Promise 并发控制
限制异步操作的并发数量,实现图片资源的批量加载
问题
有 8 个图片资源的 URL 存储在数组中,需要通过 loadImg 函数下载这些图片。loadImg 接收一个 URL,返回一个 Promise,图片下载完成时 resolve,失败时 reject。
要求:任何时刻同时下载的图片数量不超过 3 个,并尽可能快地完成所有下载。
解答
核心思路是维护一个固定大小的”并发池”,当有任务完成时,立即补充新任务进来。
function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls); // 复制 urls 数组
// 初始化并发池,先启动 limit 个任务
let promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
return index; // 返回下标,用于标识哪个任务完成了
});
});
// 使用 reduce 遍历剩余的 urls
return sequence
.reduce((pCollect, url) => {
return pCollect
.then(() => {
return Promise.race(promises); // 等待最快完成的任务
})
.then(fastestIndex => {
// 用新任务替换已完成的任务
promises[fastestIndex] = handler(url).then(() => {
return fastestIndex; // 继续返回下标供下次使用
});
});
}, Promise.resolve())
.then(() => {
// 等待最后剩余的任务全部完成
return Promise.all(promises);
});
}
// 使用示例
const urls = [
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
// ... 更多 URL
];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = reject;
img.src = url;
});
}
limitLoad(urls, loadImg, 3).then(() => {
console.log('所有图片加载完成');
});
关键点
- 使用
Promise.race找出最快完成的任务,立即用新任务替换它,保持并发数恒定 - 通过返回任务下标来定位并发池中哪个位置的任务已完成
reduce串行处理剩余任务的补充逻辑,最后用Promise.all等待所有任务完成- 初始化时先启动
limit个任务填满并发池,后续任务采用”完成一个补充一个”的策略
目录