我對請求做了個效能小最佳化,提升了50%的頁面效能

發表於2023-09-24

小冊

這是我整理的學習資料,非常系統和完善,歡迎一起學習

背景

最近海外應用有某些使用者反饋,開啟頁面比較卡頓,後來針對這個問題做了層最佳化

問題

這裡我們用微信好友列表為例子,因為列表功能比較常見,詳細分析下常見專案存在的一些問題,以及如何最佳化

image.png

通常我們的專案中都是有列表這種場景,然後點選列表裡面的具體item,就去到具體的詳情頁

image.png

我們可能是這麼處理

<List>
    {
      list.map((item)=><ListItem id={Item.useId}/>)
    } 
</List>

然後我們點選好友列表進入具體的詳情頁根據useId再去拿具體的資訊

getUserInfoById(id)

預載入

但是這裡就會存在一個,進入詳情頁的時候,開啟會慢,所以這裡一般會先做資料預載入,也就是在好友列表的時候我就想拿到這個詳情頁資料,這時候我們可能這麼處理

一次性返回資料

<List>
    {
      list.map((item)=><ListItem detail={Item.detail}/>)
    } 
</List>

後端支援在好友列表的時候同時返回具體的detail資訊,這樣就不用去走一次getUserInfoById(id),但是這裡也會存在一個問題,好友列表這個介面太冗餘,而且資料量太大,開啟頁面的時候也會出現載入慢的場景,所以這個策略也只能針對資料量較小的情況採取

預載入getUserInfoById介面

image.png

那麼幹脆一點,我們請求完好友介面後,再根據使用者Id,在App下偷偷請求getUserInfoById介面
getUserInfoById(1)
getUserInfoById(2)
getUserInfoById(3)
...
````

這樣就會出現一個問題,後端服務可能扛不住我們這樣頻繁的請求,所以有什麼辦法解決呢?那就是`請求合併`,`將多個重複請求(引數不一樣),合併成一個,也就是將引數合併`

## 請求合併

const fetchUserInfoBatched = createBatchedRequest<string, UserBaseInfo>(
async (userIds) => {

const { data } = await request.post('/api/user/list', {
  userIds,
});
return data;

},
500 // 設定延遲時間為500毫秒
);

// 使用示例
async function getUserInfo() {
const user1 = await fetchUserInfoBatched(1);
const user2 = await fetchUserInfoBatched(2);
const user3 = await fetchUserInfoBatched(3);

console.log(user1, user2, user3);
}

getUserInfo();


### createBatchedRequest

interface BatchRequestItem<T, R> {
params: T;
resolve: (r: R) => void;
reject: (reason: unknown) => void;
}

/**

  • 建立批次請求的函式
  • 在一定延遲時間內的所有請求都會被合併提交併批次傳送
  • @param batchFunction 合併後的請求函式
  • @param delay 延遲時間,以毫秒為單位
    */

export function createBatchedRequest<T, R>(
batchFunction: (batchParams: T[]) => Promise<R[]>,
delay = 200
): (params: T) => Promise<R> {
const batchQueue: BatchRequestItem<T, R>[] = [];
let isBatching = false;
let timer: NodeJS.Timeout | null = null;

async function executeBatchedRequest() {

if (isBatching) return;
isBatching = true;

const itemsToBatch = [...batchQueue];
batchQueue.length = 0;

try {
  const batchedResult = await batchFunction(itemsToBatch.map((item) => item.params));
  itemsToBatch.forEach((item, index) => {
    item.resolve(batchedResult[index]);
  });
} catch (error) {
  itemsToBatch.forEach((item) => {
    item.reject(error);
  });
} finally {
  isBatching = false;
}

}

return (params: T): Promise<R> => {

return new Promise<R>((resolve, reject) => {
  batchQueue.push({
    params,
    resolve,
    reject,
  });

  // Execute the batched request after the specified delay
  if (!timer) {
    timer = setTimeout(() => {
      executeBatchedRequest();
      timer = null;
    }, delay);
  }
});

};
}


*   **批次請求管理**: `createBatchedRequest` 函式用於管理批次請求,它可以將多個獨立的請求合併成一個批次請求,以減少不必要的網路請求次數。

*   **引數說明**:
    *   `batchFunction` 引數是一個函式,接受一個陣列 `batchParams` 作為引數,返回一個 Promise,用於處理合併後的請求並返回結果。
    *   `delay` 參數列示延遲時間,以毫秒為單位。在指定的延遲時間內,所有的請求會被收集起來,然後一次性傳送給 `batchFunction` 處理。

*   **請求佇列**: 函式內部維護一個請求佇列 `batchQueue`,用於儲存待合併的請求項。每個請求項包含了請求的引數、成功回撥函式 `resolve` 和失敗回撥函式 `reject`。

*   **執行批次請求**:
    *   當有請求呼叫返回的函式時,它會將請求引數和相應的回撥函式新增到請求佇列 `batchQueue` 中。
    *   使用定時器控制,在指定的延遲時間後,會執行 `executeBatchedRequest` 函式。
    *   `executeBatchedRequest` 函式會檢查是否已經有批次請求正在處理(`isBatching` 標誌),如果有,則不進行處理,直到當前批次請求完成。
    *   如果沒有正在處理的批次請求,它會取出請求佇列中的所有請求項,合併引數後呼叫 `batchFunction` 處理請求。
    *   成功或失敗後,會分別呼叫請求項中的 `resolve` 或 `reject` 回撥函式,將結果返回給每個獨立的請求。

## 面試

> 最近整理了一套面試小冊,有`線上版和離線版本`

離線版本效果如下,可新增微信`linwu-hi`獲取,閱讀效果非常不錯


![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/886db51df5a64001bbaf66c2dcf7884a~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=480\&h=480\&e=png\&b=faf7f7)

![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac45d166a77942df95bbb0552d1b8208~tplv-k3u1fbpfcp-jj-mark:1200:0:0:0:q75.avis)

相關文章