這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 前兩天我的新同事告訴我一個困擾著他的問題,就是低代碼平臺中存在很多模塊,這些模塊的渲染是由模塊自身處理的,簡言之就是組件請求了自己的數據,一個兩個模塊還好,要是一次請求了幾十個模塊,就會出現請求阻塞的問題,而且模塊的請求都特別大。 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
前兩天我的新同事告訴我一個困擾著他的問題,就是低代碼平臺中存在很多模塊,這些模塊的渲染是由模塊自身處理的,簡言之就是組件請求了自己的數據,一個兩個模塊還好,要是一次請求了幾十個模塊,就會出現請求阻塞的問題,而且模塊的請求都特別大。
大量的併發請求會導致網路擁塞和帶寬限制。特別是當網路帶寬有限時,同時發送大量請求可能會導致請求之間的競爭,從而導致請求的響應時間延長。
因此模塊的載入就很不順暢。。。
為瞭解決這個問題我設計了一個關於前端實現併發請求限制的方案,下麵將詳細解釋這個併發請求限制的方案及實現源碼。
核心思路及簡易實現
一、收集需要併發的介面列表,併發數,介面調用函數。
二、遍歷arr,對於每一項,創建一個promise實例存儲到resArr中,創建的時候就已經開始執行了。
三、將創建的promise傳入的實例數組中,對於每一項的promise設置其then操作,並將其存儲到running數組中,作為執行中的標識。
四、當then操作觸發之後則將running中的對應這一項刪除,執行中的數組減一。
五、在遍歷的回調函數最後判斷當前是否超出閾值,當數量達到限制時開始批量執行,用await去處理非同步,處理完一個即跳走,重新往running中註入新的實例。
async function asyncLimit(limitNum, arr, fn) { let resArr = []; // 所有promise實例 let running = []; // 執行中的promise數組 for (const item of arr) { const p = Promise.resolve(fn(item)); // 遍歷arr,對於每一項,創建一個promise實例存儲到resArr中,創建的時候就已經開始執行了 resArr.push(p); if (arr.length >= limitNum) { // 對於每一項設置其then操作,並將其存儲到running數組中,作為執行中的標識,當then操作觸發之後則將running中的對應這一項刪除,執行中的數組減一 const e = p.then(() => running.splice(running.indexOf(e), 1)); running.push(e); if (running.length >= limitNum) { // 當數量達到限制時開始批量執行,處理完一個即跳走,重新往running中註入新的實例 await Promise.race(running); } } } return Promise.allSettled(resArr); }用例:
fn = (item) => { return new Promise((resolve) => { console.log("開始",item); setTimeout(() => { console.log("結束", item); resolve(item); }, item) }); }; asyncLimit(2, [1000, 2000, 5000, 2000, 3000], fn)
註:但是這裡的實現太過簡陋,在真正的業務場景中往往沒有這樣使用場景,因此我對著段代碼封裝成一個符合前端使用的併發限制模塊,下麵是完整可用的代碼實現
完整實現及源碼用例
首先,讓我們來看一下代碼及用例:
let targetArray = []; // 目標調用數組 let resultArray = []; // 結果數組 let runningPromise = null; // 正在運行的 Promise let limitNum = 0; // 最大併發數 let defaultFn = (value) => value; // 預設處理函數 function asyncInit(limit, fn) { limitNum = limit; defaultFn = fn; } async function asyncLimit(arr) { const promiseArray = []; // 所有 Promise 實例 const running = []; // 正在執行的 Promise 數組 for (const item of arr) { const p = Promise.resolve((item.fn || defaultFn)(item.value || item)); // 調用元素的處理函數 promiseArray.push(p); if (arr.length >= limitNum) { const e = p.then(() => running.splice(running.indexOf(e), 1)); running.push(e); if (running.length >= limitNum) { await Promise.race(running); } } } return Promise.allSettled(promiseArray); } function asyncExecute(item) { targetArray.push(item); if (!runningPromise) { runningPromise = Promise.resolve().then(()=>{ asyncLimit(targetArray).then((res) => { resultArray.push(...res); targetArray = []; runningPromise = null; }); }) } }
這裡提供了一個併發模塊的文件。
簡單用例:
asyncInit(3, (item) => { return new Promise((resolve) => { console.log("開始",item); setTimeout(() => { console.log("結束", item); resolve(item); }, item) }); }) asyncExecute({value: 1000}) asyncExecute({value: 2000}) asyncExecute({value: 5000}) asyncExecute({value: 2000}) asyncExecute({value: 3000})
效果:
註:可以看到我們在使用的時候只需要先初始化最大併發數和預設調用函數,即可直接調用asyncExecute去觸發併發請求,而且通過源碼我們可以看到如果 asyncExecute 的參數可以自定義調用函數,及傳入的對象中包含fn即可。
重點: 因為這些內容都被抽離成一個文件,所以我們可以導出asyncExecute
這個函數然後業務側不同位置都可以通過這個函數去發起請求,這樣就能實現項目中所有請求的併發限制。
代碼解釋
這段代碼實現了一個前端併發限制的機制。讓我們逐步解釋其中的關鍵部分。
第一步
我們定義了一些變數,包括目標調用數組 targetArray
、結果數組 resultArray
、正在運行的 Promise runningPromise
、最大併發數 limitNum
和預設處理函數 defaultFn
。
第二步
定義 asyncInit
函數,用於初始化併發限制的最大數和預設處理函數。通過調用該函數,我們可以設置相關併發限制的參數。
第三步
然後,我們定義了 asyncLimit
函數,用於實現併發限制的核心邏輯。
在這個函數中,我們遍歷傳入的數組 arr
,對每個元素執行處理函數,並將返回的 Promise 實例存儲在 promiseArray
數組中。
同時,我們使用 running
數組來跟蹤正在執行的 Promise 實例。
如果當前正在執行的 Promise 實例數量達到最大併發數,我們使用 Promise.race
方法等待最先完成的 Promise,以確保併發數始終保持在限制範圍內。(這裡對應的就是核心思路及簡易實現
中的代碼)
註:如果要實現非同步併發,我們只要保證我們的介面存在於傳入的數組即arr
中即可。
第四步
定義 asyncExecute
函數,用於觸發非同步操作的執行。
當調用 asyncExecute
函數時,我們將目標元素添加到 targetArray
數組中,這個targetArray就是非同步並行的介面隊列,只要把這個傳入到asyncLimit中就能實現非同步並行。
檢查是否有正在運行的 runningPromise
。
(runningPromise的
作用:
判斷當前是否已經有運行中的asyncLimit
)
如果有那麼我們只需要繼續往targetArray
中加入數據即可,沿用之前的asyncLimit
即之前的Promise 鏈。
如果沒有說明asyncLimit
函數已經執行完了,我們要新開一個asyncLimit
函數去完成我們的並行限制。調用 asyncLimit
函數來處理目標數組中的元素,並基於此創建一個新的 Promise 鏈。
處理完成後,我們將結果存儲在 resultArray
中,並重置目標數組和運行的 Promise。
總結
非同步並行邏輯交由asyncLimit
處理即可。
使用上來說,就只需要使用到介面的時候,調用asyncExecute
往 targetArray
加數據就行,預設會直接執行 asyncLimit
並創建一個promise鏈。
當我們往裡面加一項promise鏈就會對應的多一項,當我們promise鏈執行完之後我們就會重置targetArray
和runningPromise
。
下次調用asyncExecute
時,如果runningPromise
不存在就重新走上面的邏輯,即直接執行 asyncLimit
並創建一個promise鏈,當runningPromise
存在的情況下,每次使用asyncExecute
往targetArray
裡面push參數即可。