我對請求做了個(gè)性能小優(yōu)化,提升了50%的頁面性能
背景
最近海外應(yīng)用有某些用戶反饋,打開頁面比較卡頓,后來針對這個(gè)問題做了層優(yōu)化
問題
這里我們用微信好友列表為例子,因?yàn)榱斜砉δ鼙容^常見,詳細(xì)分析下常見項(xiàng)目存在的一些問題,以及如何優(yōu)化
通常我們的項(xiàng)目中都是有列表這種場景,然后點(diǎn)擊列表里面的具體item,就去到具體的詳情頁
我們可能是這么處理
<List>
{
list.map((item)=><ListItem id={Item.useId}/>)
}
</List>
然后我們點(diǎn)擊好友列表進(jìn)入具體的詳情頁根據(jù)useId再去拿具體的信息
getUserInfoById(id)
預(yù)加載
但是這里就會(huì)存在一個(gè),進(jìn)入詳情頁的時(shí)候,打開會(huì)慢,所以這里一般會(huì)先做數(shù)據(jù)預(yù)加載,也就是在好友列表的時(shí)候我就想拿到這個(gè)詳情頁數(shù)據(jù),這時(shí)候我們可能這么處理
一次性返回?cái)?shù)據(jù)
<List>
{
list.map((item)=><ListItem detail={Item.detail}/>)
}
</List>
后端支持在好友列表的時(shí)候同時(shí)返回具體的detail信息,這樣就不用去走一次getUserInfoById(id),但是這里也會(huì)存在一個(gè)問題,好友列表這個(gè)接口太冗余,而且數(shù)據(jù)量太大,打開頁面的時(shí)候也會(huì)出現(xiàn)加載慢的場景,所以這個(gè)策略也只能針對數(shù)據(jù)量較小的情況采取
預(yù)加載getUserInfoById接口
那么干脆一點(diǎn),我們請求完好友接口后,再根據(jù)用戶Id,在App下偷偷請求
getUserInfoById接口
getUserInfoById(1)
getUserInfoById(2)
getUserInfoById(3)
...
這樣就會(huì)出現(xiàn)一個(gè)問題,后端服務(wù)可能扛不住我們這樣頻繁的請求,所以有什么辦法解決呢?那就是請求合并,將多個(gè)重復(fù)請求(參數(shù)不一樣),合并成一個(gè),也就是將參數(shù)合并
請求合并
const fetchUserInfoBatched = createBatchedRequest<string, UserBaseInfo>(
async (userIds) => {
const { data } = await request.post('/api/user/list', {
userIds,
});
return data;
},
500 // 設(shè)置延遲時(shí)間為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;
}
/**
* 創(chuàng)建批量請求的函數(shù)
* 在一定延遲時(shí)間內(nèi)的所有請求都會(huì)被合并提交并批量發(fā)送
* @param batchFunction 合并后的請求函數(shù)
* @param delay 延遲時(shí)間,以毫秒為單位
*/
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函數(shù)用于管理批量請求,它可以將多個(gè)獨(dú)立的請求合并成一個(gè)批量請求,以減少不必要的網(wǎng)絡(luò)請求次數(shù)。 -
參數(shù)說明:
-
batchFunction參數(shù)是一個(gè)函數(shù),接受一個(gè)數(shù)組batchParams作為參數(shù),返回一個(gè) Promise,用于處理合并后的請求并返回結(jié)果。 -
delay參數(shù)表示延遲時(shí)間,以毫秒為單位。在指定的延遲時(shí)間內(nèi),所有的請求會(huì)被收集起來,然后一次性發(fā)送給batchFunction處理。 -
請求隊(duì)列: 函數(shù)內(nèi)部維護(hù)一個(gè)請求隊(duì)列
batchQueue,用于存儲(chǔ)待合并的請求項(xiàng)。每個(gè)請求項(xiàng)包含了請求的參數(shù)、成功回調(diào)函數(shù)resolve和失敗回調(diào)函數(shù)reject。 -
執(zhí)行批量請求:
-
當(dāng)有請求調(diào)用返回的函數(shù)時(shí),它會(huì)將請求參數(shù)和相應(yīng)的回調(diào)函數(shù)添加到請求隊(duì)列 batchQueue中。 -
使用定時(shí)器控制,在指定的延遲時(shí)間后,會(huì)執(zhí)行 executeBatchedRequest函數(shù)。 -
executeBatchedRequest函數(shù)會(huì)檢查是否已經(jīng)有批量請求正在處理(isBatching標(biāo)志),如果有,則不進(jìn)行處理,直到當(dāng)前批量請求完成。 -
如果沒有正在處理的批量請求,它會(huì)取出請求隊(duì)列中的所有請求項(xiàng),合并參數(shù)后調(diào)用 batchFunction處理請求。 -
成功或失敗后,會(huì)分別調(diào)用請求項(xiàng)中的 resolve或reject回調(diào)函數(shù),將結(jié)果返回給每個(gè)獨(dú)立的請求。
