JavaScript 虽说是单线程的,但是由于存在异步任务任务队列,所以还是会遇到和后端语言(比如 Java)一样的并发控制问题
在平时开发中,实现并发最常用的就是使用Promise.all()
,但是Promise.all()
有一个很大的缺点,就是所有任务都是同时进行无法对任务进行并发控制
一个最常见的需求如批量上传图片,在选中多张图片后期望并发上传,同时考虑到客户端网络和服务端负载情况(假设选了 100 张图片,同时上传肯定会有超时错误产生),期望有同时运行的上传任务不超过 2
基于 Promise 可以写出如下的任务队列
class TaskQueue {
constructor(maxNum) {
this.list = []
this.maxNum = maxNum
this.workingNum = 0
}
add(asyncFunction) {
this.list.push(asyncFunction)
this.run()
}
run() {
for (let i = 0; i < this.maxNum; i++) {
this.doNext()
}
}
doNext() {
if (this.list.length == 0 && this.workingNum == 0) {
console.timeEnd('task')
}
if (this.list.length && this.workingNum < this.maxNum) {
this.workingNum++
const asyncFunction = this.list.shift()
asyncFunction().finally(() => {
this.workingNum--
this.doNext()
})
}
}
}
测试代码
function createTask(name, delay) {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name, 'done')
resolve()
}, delay)
})
}
}
const queue = new TaskQueue(2)
console.time('task')
queue.add(createTask('task1', 1000))
queue.add(createTask('task2', 1000))
queue.add(createTask('task3', 1000))
queue.add(createTask('task4', 1000))
拓展
在百度的过程中发现这个和字节的一个面试题很像 某条高频面试原题:实现有并行限制的 Promise 调度器
另 Github 上也有现成的方案 sindresorhus/p-queue 可以实现更加强大的功能。发现作者基于 promise 封装了好多有意思的工具库 sindresorhus/promise-fun,有兴趣的可以学习下
上面的问题使用p-queue
可以更加优雅地完成
const queue = new PQueue({ concurrency: 2, autoStart: true })
console.time('task')
queue.add(createTask('task1', 1000))
queue.add(createTask('task2', 1000))
queue.add(createTask('task3', 1000))
queue.add(createTask('task4', 1000))
await queue.onIdle()
console.timeEnd('task')