短小且優雅的Promise併發控制實現
前言
Promise是前端工程師寫程式碼最常用的知識點,也是大廠面試最愛考察的點。在做大廠校招面試整理的時候做個初略多統計,700份面經裡有234份面經出現Promise。而手寫程式碼實現Promise的併發控制算是其中出現頻繁且稍微難度高一點的題目。
實現
這裡給出一個參考實現,出自這裡,程式碼只有十幾行,但實現的非常巧妙。
async function asyncPool(poolLimit, array, iteratorFn) { const ret = []; //2 const executing = []; //3 for (const item of array) { //4 const p = Promise.resolve().then(() => iteratorFn(item)); //5 ret.push(p); //6 if (poolLimit <= array.length) { //7 const e = p.then(() => executing.splice(executing.indexOf(e), 1)); //8 executing.push(e); //9 if (executing.length >= poolLimit) { //10 await Promise.race(executing); //11 } } } return Promise.all(ret); //15 }
程式碼雖然不多,但需要對Promise非常熟悉才能理解,下面就模擬程式碼執行跑一遍執行過程。
假設 poolLimit = 3, array是一個長度為10的url列表, iteratorFn是一個返回Promose物件的函式用於傳送請求,模擬一下執行過程:
- line2:建立陣列ret,用於存放全部的Promise物件
- line3:建立陣列execting,用於存放併發限制的處於Pending狀態的Promise物件
- line4:item是array的第一項
- line5: iteratorFn(item) 得到一個pending狀態的Promise物件 p。(這裡之所以不直接 p = iteratorFn(item),是為了相容iteratorFn是同步函式的場景,保證返回的p一定是Promose物件,見下方測試程式碼)
- line6:p放入ret
- line7:如果限制數量poolLimit 小於等於 陣列的總長度再執行限制。當前poolLimit=3,arr.length=10,進入if邏輯
- line8 :根據剛剛的p建立一個Promise物件e,等p resolve的時候才執行then裡的回撥,把e從executing陣列移除(PS:目前e還沒放入陣列,在line9會放進去)
- line9:把e放入executing
- line10:目前executing長度小於poolLimit限制長度3,不進入if,回到line4執行下一次迴圈
- .... 迴圈執行到第3次時,到達line10,此時ret陣列為[p1_ pending, p2_pending , p3_ pending],executing陣列為[e1_pending, e2_pending, e3_pending],其中p1_pending 的resolve會觸發e1的移出和resolve(第8行then裡的箭頭函式執行完e就resovle)
- line11:卡住,等待executing 數組裡的[e1,e2,e3]看哪個最快resolve 。假設p2最先resolve,p2的resolve觸發e2的resolve,當e2 resolve 之後,Promise.race(executing)得到結果, 此刻回到line4,for迴圈才進入下一輪,execting數組裡為[e1_ pending, e3_pending ],ret陣列為[p1 pending, p2_fulfilled, p3_pending ]。
- line4:... ,繼續下一輪迴圈,execting陣列始終保持最多不超過3個
- ...
- line15:當for迴圈結束之後,ret數組裡包含全部arr封裝的promise物件,返回Promise.all(ret),得到新的Promise物件(等ret所有的全resolve後該Promise物件才resolve,同時得到所有資料)
測試
以下是測試程式碼:
const curl = (i) => { console.log('開始' + i); return new Promise((resolve) => setTimeout(() => { resolve(i); console.log('結束' + i); }, 1000+Math.random()*1000)); }; /* const curl = (i) => { console.log('開始' + i); return i; }; */ let urls = Array(10).fill(0).map((v,i) => i); (async () => { const res = await asyncPool(3, urls, curl); console.log(res); })();
飢人谷試學營還在進行中,系統班年前最後一波,儘早上車敢2022春招末尾或者7月份提前批。
「其他文章」
- 短小且優雅的Promise併發控制實現
- 一次性弄懂CSS3 3D(perspective、transform-style、backface-visibility)
- 最全的TypeScript學習指南
- 零基礎可操作的前端入門學習指南
- 一份小白前端視覺化學習指南——附思維導圖
- 如何一次性載入10萬條資料(虛擬長列表)
- 當問到VueRouter兩種模式和原理時
- 前路漫漫,寫給初級前端的學習指南
- 當讓你封裝一個事件代理函式時
- 前端輪迴
- JavaScript 面試題一則:有限並行
- 面試官叫我手寫 redux-thunk
- 前端視覺化之 SVG 手冊速覽
- 我做前端1年了,工作之餘我該如何做自我提升
- 面試官叫我手寫 Redux - 1
- px、em、rem、vw、百分比的區別「前端劍指offer」
- 物理畫素、邏輯畫素、CSS畫素、PPI、裝置畫素比是什麼「前端劍指offer」
- 手寫bind(手寫系列八)
- 寫給前端程式設計師的英文學習指南 | 掘金技術徵文-雙節特別篇
- 28 個JavaScript程式設計黑科技,裝逼指南,高逼格程式碼,讓你驚歎不已