前端性能優化之控制請求並發數
在我們平時開發中,經常會遇到頁面數據初始化時,頻繁調同一個接口的情況。比如echarts項目中,一個頁面可能會有幾十張圖表,如果一個接口返回所有圖表數據的話,會造成用戶過長的等待時間,再者過多圖表同時渲染,也會給頁面增加壓力,造成卡頓的現象。
我們通常會讓每個圖表單獨調一個接口,入參不同,這樣更有利於頁面快速渲染圖表,單個圖表請求到數據,立即渲染,不需要等待其他圖表。可理想很豐滿,現實很骨感,當服務器配置過低,或者後端代碼性能較弱,會難以處理這些並發請求,接口調用越多,等待處理的時間可能就越長,甚至超過一次性返回所有數據的間。。。為了解決這種問題,緩解後端壓力,本篇將介紹前端來控制請求的並發數:
先分析一波,假設我們需要重複調用30次接口,並聯調用接口,服務端壓力較大,可能會造成響應時間過長。逐漸減少並發數,假設並發數為5的時候,服務器處理速度最快,幾乎不受並發影響。
針對這種情況,我們可以封裝接口請求方法,控制每次接口請求的並發數,將30次分解成:並發數為5,分6次請求。這樣的話,服務器每次處理5次請求,資源釋放出來繼續處理下一批請求,從而解決並發擁堵問題~
初步構思:
class TaskQueue { constructor(max) { this.max = max; this.taskList = []; } addTask(task) { this.taskList.push(task); } } function createTask(i) { return () => { return new Promise((resolve, reject) => { setTimeout(() => { if (i == 4 || i == 15) { reject("出錯啦~"); } else { resolve("成功呀~" + i); } }, 2000); }); }; } const taskQueue = new TaskQueue(5); for (let i = 0; i < 30; i++) { const task = createTask(i); taskQueue.addTask(task); }
for循環調用函數createTask()返回30個promise的異步任務,任務隊列TaskQueue類返回一個實例,控制這30個異步任務的並發,構造器中傳入並發數5。
接下來用TaskQueue實現控制並發:
class TaskQueue { constructor(max) { this.max = max; // 並發數 this.min = 0; this.taskList = []; // 全部任務 Promise.resolve().then(() => this.run()) // 等同步代碼(addTask)全部執行完成,再執行run } // 增加任務 addTask(task) { this.taskList.push(task); } // 執行任務 async run() { if (!this.taskList.length) return; const AsyncTasks = []; this.min = Math.min(this.max, this.taskList.length) // 當傳入的並發數大於任務數,取任務數, 反之取並發數 // 根據並發數分組 for(let i = 0; i < this.min; i++) { AsyncTasks.push(this.taskList.shift()); } await this.handleTask(AsyncTasks); // 通過下面遞歸,這裡將會有6個異步任務串聯執行 this.run(); // 遞歸 } async handleTask(tasks) { // 返回promise處理異步任務組 return new Promise(resolve => { // 遍歷任務組,5個異步任務並聯執行 tasks.forEach(async (task, index) => { await task().then(res => { console.log(res); }).catch((err) => { console.log(err); }).finally(() => { index + 1 === this.min && console.log('==============================='); index + 1 === this.min && resolve() // 最後一個任務resolve(),promise完成 }) }) }) } } function createTask(i) { return () => { return new Promise((resolve, reject) => { setTimeout(() => { if (i == 4 || i == 15) { // 測試捕捉錯誤 reject("出錯啦~"); } else { resolve("成功呀~" + i); } }, 2000); }); }; } const taskQueue = new TaskQueue(5); for (let i = 0; i < 30; i++) { const task = createTask(i); taskQueue.addTask(task); }
試試效果:
nice,至此,30次異步任務,分6次完成,每次處理5個,大家可以在此基礎上拓展請求接口,並增加一些處理邏輯,歡迎留言探討~
腳踏實地行,海闊天空飛~