使用Promise控制任务并发数量

120 阅读2分钟

有些应用场景,需要同时进行几十个请求,例如爬取一些网站的所有资源的时候。这时候就需要控制并发的数量.

例如每次只执行三个任务,一个任务执行完后,再从任务列表中取出一个继续执行,这样能保证一次只有三个任务在执行.

每个任务可以看做一个函数,这个函数返回一个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.racePromise.allSettledSet()

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')
    })
}

经测试仍然符合预期.