简述
当我们需要保证代码在多个异步操作都完成后执行,通常我们会使用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))
输出为
[
"get: pic_1.png",
"get: pic_2.png",
"get: pic_3.png",
"get: pic_4.png",
"get: pic_5.png",
"get: pic_6.png"
]
如果我们对并行的请求数量有限制,Promise.all 自身是不具有这个功能的。
那么接下来,我们依旧以上述的fetchImage为例,来实现一个可以对图片请求进行并行限制的函数:
/**
* @description 带并发限制的图片并发请求
* @param {Array} imageUrls 待请求的图片url列表
* @param {Object} limit 最大并发个数限制
* @return { Promise<Array> } resList
*/
function fetchImageWithLimit(imageUrls, limit = 4)
本文提供两个思路解决该问题:
- 通过
Promise.all来实现 - 通过
Promise.race来实现
通过Promise.all实现
步骤如下:
- 初始化
limit个Promise对象,作为Promise.all的参数 - 每个Promise对象去
imageUrls中取出一个url进行请求,若无则resolve - 每个Promise对象在当前请求成功后重复步骤2
function fetchImageWithLimit(imageUrls, limit) {
// 待请求的url的索引
let currentIndex = 0
// 用来记录数组 index - response 的映射
// 保证输出列表与输入顺序一致
let rs = new Map()
// 递归的去取url进行请求
function run() {
if (currentIndex >= imageUrls.length) {
return Promise.resolve()
}
// 记下当前的索引,然后索引+1
const index = currentIndex++
const url = imageUrls[index]
// 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(index, res)
return run()
})
}
// 当imageUrls.length < limit的时候,我们也没有必要去创建多余的Promise
const promiseList = Array(Math.min(limit, imageUrls.length))
// 这里用Array.protetype.fill做了简写,但不能进一步简写成.fill(run())
.fill(Promise.resolve())
.map(promise => promise.then(run))
return Promise.all(promiseList).then(
() => imageUrls.map((_item, index) => rs.get(index))
)
}
去掉代码中console.log的注释,可展示出如下结果:
fetchImageWithLimit(imageUrls, 2)
.then(res => console.log(res))
.catch(err => console.error(err))
// 过程中输出依次为(次序每次都可能不一样)
pic_1.png [start at] 746
pic_2.png [start at] 746
pic_1.png [end at] 814
pic_3.png [start at] 814
pic_3.png [end at] 1329
pic_4.png [start at] 1330
pic_2.png [end at] 1460
pic_5.png [start at] 1460
pic_4.png [end at] 2172
pic_6.png [start at] 2172
pic_5.png [end at] 2401
pic_6.png [end at] 2968
// 最终的结果输出
[
"get: pic_1.png",
"get: pic_2.png",
"get: pic_3.png",
"get: pic_4.png",
"get: pic_5.png",
"get: pic_6.png"
]
通过Promise.race实现
步骤如下:
- 初始化
limit个图片请求,作为Promise.race的参数 - 当其中任一请求结束,即新建一个图片请求(还有的话),并连同之前未结束的
limit - 1个图片请求一起作为新一轮的Promise.race的参数 - 重复步骤2,直到图片都已被请求
具体可参考 这篇博客中对于asyncPool实现的分析。
我觉得写的挺好,有空了我再来自己手撕一遍。~。~