前端性能優化之控制請求並發數

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

 

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