异步是 js 一个很是重要的特性,但不少时候,咱们不单单想让一系列任务并行执行,还想要控制同时执行的并发数,尤为是在针对操做有限资源的异步任务,好比文件句柄,网络端口等等。npm
看一个例子。数组
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // simulate an async work that takes 1s to finish async function execute(id) { console.log(`start work ${id}`); await sleep(1000); console.log(`work ${id} done`); } Promise.all([1, 2, 3, 4, 5, 6, 7, 8, 9].map(execute));
输出结果:网络
"start work 1" "start work 2" "start work 3" "start work 4" "start work 5" "start work 6" "start work 7" "start work 8" "start work 9" "work 1 done" "work 2 done" "work 3 done" "work 4 done" "work 5 done" "work 6 done" "work 7 done" "work 8 done" "work 9 done"
能够看到,全部的 work 都同时开始执行了。并发
如今,若是咱们想让这些 work 每次只执行 2 个,2 个完成以后再继续后面的 2 个,即并发数为 2 应该怎么作呢?异步
Promise
的生成是关键咱们知道,Promise.all
并不会触发Promise
的执行,真正触发执行的是建立Promise
自己,换句话说,Promise
在生成的一瞬间就已经开始执行了!所以,若是要控制Promise
的并发,咱们就要控制Promise
的生成。async
Iterator
控制并发数常见的解决方案是经过一个函数接收并发任务数组
,并发函数
,并发数
3 个参数,根据并发数,监控Promise
的完成状态,批量建立新的Promise
,从而达到控制Promise
生成的目的。ide
如今,咱们来尝试另一个思路,经过Iterator
来控制并发数。函数
Iterator
会发生什么?让咱们先来看一个简化的例子。oop
// Array.values returns an Array Iterator const iterator = [1, 2, 3].values(); for (const x of iterator) { console.log(`loop x: ${x}`); for (const y of iterator) { console.log(`loop y: ${y}`); } }
输出结果:ui
"loop x: 1" "loop y: 2" "loop y: 3"
注意到没有?y 循环接着 x 循环继续,而且 2 个循环都在全部元素遍历完以后结束了!这正是咱们要利用的特性。
对 Iterator 不熟悉的同窗能够参考 MDN 文章:https://developer.mozilla.org...
Iterator
改造 work 的例子让咱们用Iterator
的这个特性来改造最开始的 work 例子。
// generate workers according to concurrency number // each worker takes the same iterator const limit = concurrency => iterator => { const workers = new Array(concurrency); return workers.fill(iterator); }; // run tasks in an iterator one by one const run = func => async iterator => { for (const item of iterator) { await func(item); } }; // wrap limit and run together function asyncTasks(array, func, concurrency = 1) { return limit(concurrency)(array.values()).map(run(func)); } Promise.all(asyncTasks(tasks, execute, 2));
输出结果:
"start work 1" "start work 2" "work 1 done" "start work 3" "work 2 done" "start work 4" "work 3 done" "start work 5" "work 4 done" "start work 6" "work 5 done" "start work 7" "work 6 done" "start work 8" "work 7 done" "start work 9" "work 8 done" "work 9 done"
结果和咱们预想的同样,每次只同时执行两个异步任务直到全部任务都执行完毕。
不过,这个方案也不是天衣无缝。主要问题在于,若是某个 worker 在执行过程当中出错了,其他的 worker 并不会所以中止工做。也就是说,上面的例子中,若是 worker 1 出现异常中止了,worker 2 会独自执行剩下全部任务,直到所有完毕。所以,若是想要时刻保持 2 个并发,最简单的方法是给每一个execute
方法添加catch
。
尽管不够完美,将Iterator
做为控制Promise
建立,也不失为一种简单有效的控制异步并发数的简单方法。
固然,实际项目中,仍是尽可能避免重复造轮子,p-limit,async-pool甚至bluebird都是简单易用的解决方案。