前端效能優化之控制請求並行數

2022-08-01 21:08:12

  在我們平時開發中,經常會遇到頁面資料初始化時,頻繁調同一個介面的情況。比如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個,大家可以在此基礎上拓展請求介面,並增加一些處理邏輯,歡迎留言探討~

 

腳踏實地行,海闊天空飛~