有些应用场景,需要同时进行几十个请求,例如爬取一些网站的所有资源的时候。这时候就需要控制并发的数量.
例如每次只执行三个任务,一个任务执行完后,再从任务列表中取出一个继续执行,这样能保证一次只有三个任务在执行.
每个任务可以看做一个函数,这个函数返回一个Promise.
function paralleTask(tasks, paralleCount = 3) {
return new Promise((resolve) => {
if(tasks.length === 0) {
resolve()
return
}
let currentIndex = 0
function _run() {
let task = tasks[currentIndex]
currentIndex++
if (currentIndex >= tasks.length) {
resolve()
return
}
task().then((res) => {
if (currentIndex < tasks.length) {
_run()
}
})
}
for(let i = 0; i < paralleCount; i++) {
_run()
}
})
}
// 测试
let promises = []
for (let i = 0; i < 10; i++) {
promises.push(() =>
new Promise((resolve) => {
console.log(`Promise ${i} is doing...`)
setTimeout(() => {
console.log(`Promise ${i} resolve...`)
resolve()
},1000 + Math.random() * 1000);
})
)
}
paralleTask(promises, 2)
从输出可以看到一次只有两个任务在同时执行,符合预期.
23.09.21 补充:
在掘金上还看到另一种实现(链接)如下: 这种实现是维护一个并发池,任务结束后从池子里删除,添加新的任务. 因为要保证池子里任务的数量不超过n, 所以当任务数量达到n时, 需要停下来等待最快的任务完成, 再继续下一个任务.
所以想到了Promise.race.
知识点: Promise.race、Promise.allSettled、Set()
async function paralleTask(requestList,limits){
// 维护一个promise队列
const promises = []
// 当前的并发池,用Set结构方便删除
const pool = new Set() // set也是Iterable<any>[]类型,因此可以放入到race里
// 开始并发执行所有的任务
for(let request of requestList){
// 开始执行前,先await 判断 当前的并发任务是否超过限制
if(pool.size >= limits){
// 这里因为没有try catch ,所以要捕获一下错误,不然影响下面微任务的执行
await Promise.race(pool)
.catch(err=>err)
}
const promise = request()// 拿到promise
// 删除请求结束后,从pool里面移除
const cb = ()=>{
pool.delete(promise)
}
// 注册下then的任务
promise.then(cb,cb)
pool.add(promise)
promises.push(promise)
}
Promise.allSettled(promises).then(() => {
console.log('done')
})
}
经测试仍然符合预期.