有這樣一些場景:
- 頁面一加載,需要同時發 10 個請求,結果頁面卡住,服務器也快崩了。
- 用戶可以批量操作,一次點擊觸發了幾十個上傳文件的請求,瀏覽器直接轉圈圈。
當后端處理不過來時,前端一股腦地把請求全發過去,只會讓情況更糟。
核心思想就一句話:不要一次性把所有請求都發出去,讓它們排隊,一個一個來,或者一小批一小批來。
這就好比超市結賬,只有一個收銀臺,卻來了100個顧客。最好的辦法就是讓他們排隊,而不是一擁而上。我們的“請求隊列”就是這個“排隊管理員”。
直接上代碼:一個即插即用的請求隊列
不用復雜的分析,直接復制下面的?RequestPool
?類到我們的項目里。它非常小巧,只有不到 40 行代碼。
/*** 一個簡單的請求池/請求隊列,用于控制并發*?@example* const pool = new RequestPool(3); // 限制并發數為 3* pool.add(() => myFetch('/api/1'));* pool.add(() => myFetch('/api/2'));*/
class?RequestPool?{/***?@param?{number}?limit?- 并發限制數*/constructor(limit =?3) {this.limit?= limit;?// 并發限制數this.queue?= []; ???// 等待的請求隊列this.running?=?0; ??// 當前正在運行的請求數}/*** 添加一個請求到池中*?@param?{Function}?requestFn?- 一個返回 Promise 的函數*?@returns?{Promise}*/add(requestFn) {return?new?Promise((resolve, reject) =>?{this.queue.push({ requestFn, resolve, reject });this._run();?// 每次添加后,都嘗試運行});}_run() {// 只有當 正在運行的請求數 < 限制數 且 隊列中有等待的請求時,才執行while?(this.running?<?this.limit?&&?this.queue.length?>?0) {const?{ requestFn, resolve, reject } =?this.queue.shift();?// 取出隊首的任務this.running++;requestFn().then(resolve).catch(reject).finally(() =>?{this.running--;?// 請求完成,空出一個位置this._run(); ??// 嘗試運行下一個});}}
}
如何使用?三步搞定!
假設你有一個請求函數?mockApi
,它會模擬一個比較慢的接口
// 1.模擬一個慢的接口
function MockApi(id: number) {const delay = Math.random() * 1000 + 500;return new Promise((resolve) => {setTimeout(() => {console.log(`[${id}] 請求完成`);resolve(`任務${id}的結果`);}, delay);});
}
// 2. 創建一個請求池,限制并發為 2const pool = new RequesetPool(2);
// 3. 把你的請求扔進去
for (let i = 0; i < 10; i++) {pool.add(() => MockApi(i)).then((result: any) => console.log(`[${i}] 收到的結果:${result}`));
}
發生了什么?
當你運行上面的代碼,你會看到:
[1]
?和?[2]
?的請求幾乎同時開始。[3]
、[4]
、[5]
、[6]
?在乖乖排隊。- 當?
[1]
?或?[2]
?中任意一個完成后,隊列中的?[3]
?馬上就會開始。 - 整個過程,同時運行的請求數永遠不會超過 2 個。
控制臺輸出類似這樣:
發生了什么?
當你運行上面的代碼,你會看到:
[1]
?和?[2]
?的請求幾乎同時開始。[3]
、[4]
、[5]
、[6]
?在乖乖排隊。- 當?
[1]
?或?[2]
?中任意一個完成后,隊列中的?[3]
?馬上就會開始。- 整個過程,同時運行的請求數永遠不會超過 2 個。
控制臺輸出類似這樣: