Promise 实现并发控制

88 阅读2分钟

编写一个函数, 接受一个Promise数组, 通过参数控制最大并发数, 所有任务结束后通过回调得到最终结果。直接上代码, 逻辑都在注释里解释清楚了。

function run(list, num, callback) {
    if (num === 0 || num >= list.length) {
        // 如果并发数设置为 0 或者大于当前任务数, 直接使用 allSettled 全部执行
        const tasks = list.map(ele => ele())
        Promise.allSettled(tasks).then(res => {
            callback(res)
        })
        return
    }
    const results = [] // 所有任务的结果, 需要保证顺序与 list 中任务顺序相同
    let runningTasks = 0 // 当前运行中的任务

    /**
     * 启动一个任务
     */
    const next = () => {
        let len = list.length
        if (!len) {
            return // 没有待启动的任务时直接返回
        }

        // 取出一个任务, 并且记录其在 list 中的索引
        let index = len - 1
        let task = list.pop()
        // 启动任务, 返回值仿照 allSettled
        task().then(res => {
            results[index] = {
                status: 'fulfilled',
                value: res
            }
        }).catch(err => {
            results[index] = {
                status: 'rejected',
                reason: err
            }
        }).finally(() => {
            runningTasks-- // 完成一个任务的时候减一
            next() // 结束一个任务后再启动一个任务
            if (runningTasks === 0) {
                callback(results) // 如果运行中的任务数为 0 ,则表示所有任务都结束了, 使用回调传出结果
            }
        })
        runningTasks++ // 启动一个任务的时候加一
        if (runningTasks < num) {
            // 初次运行时, 如果当前运行中的任务数小于并发数, 需要补齐启动的任务数到 num
            next()
        }
    }

    next() // 启动任务
}

let tasks = new Array(10).fill(0).map((ele, index) => {
    return () => new Promise((resolve, reject) => {
        console.log(`running ${index}`)
        setTimeout(() => {
            console.log(`done ${index}`)
            if (Math.random() > 0.5) {
                resolve(`p${index}`)
            } else {
                reject(`p${index}`)
            }
        }, Math.random() * 3000)
    })
})

run(tasks, 3, console.log)

运行结果:

// running 9
// running 8
// running 7
// done 7
// running 6
// done 8
// running 5
// done 9
// running 4
// done 5
// running 3
// done 6
// running 2
// done 4
// running 1
// done 2
// running 0
// done 3
// done 1
// done 0
// [
//   { status: 'fulfilled', value: 'p0' },
//   { status: 'fulfilled', value: 'p1' },
//   { status: 'rejected', reason: 'p2' },
//   { status: 'fulfilled', value: 'p3' },
//   { status: 'rejected', reason: 'p4' },
//   { status: 'fulfilled', value: 'p5' },
//   { status: 'fulfilled', value: 'p6' },
//   { status: 'fulfilled', value: 'p7' },
//   { status: 'rejected', reason: 'p8' },
//   { status: 'fulfilled', value: 'p9' }
// ]