異步是 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都是簡單易用的解決方案。