文章中英模式
布魯斯前端JS面試題目 - 實作 Promise Pool 並發控制
學習如何實作 Promise Pool 控制並發執行的非同步任務,解決高併發問題,提升前端應用性能與面試競爭力。
文章中英模式
懶得看文章?那就來看影片吧
Promise Pool 概述
Promise Pool(Promise 池)是一種控制並發執行 Promise 的技術,主要用於限制同時執行的非同步任務數量。在處理大量非同步操作時(如 API 請求、檔案處理等),它可以有效防止系統過載並優化資源使用。
Promise Pool 的主要特點
- 1.
控制並發數
:限制同時執行的 Promise 數量 - 2.
動態調度
:一個任務完成後自動開始下一個 - 3.
按序提交
:任務按順序提交,但不保證按順序完成 - 4.
錯誤處理
:可以處理單個任務的失敗而不影響其他任務 - 5.
資源優化
:防止系統資源過度消耗
Promise Pool 運作原理圖解
任務佇列: [Task1, Task2, Task3, Task4, Task5, Task6, ...]
│
▼
┌───────────────────────────────────┐
│ Promise Pool (並發數=3) │
├───────────┬───────────┬───────────┤
│ Task1 │ Task2 │ Task3 │ ← 同時執行的任務
│ 執行中 │ 執行中 │ 執行中 │
├───────────┴───────────┴───────────┤
│ │
│ Task4, Task5, ... 等待執行 │
│ │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Promise Pool (並發數=3) │
├───────────┬───────────┬───────────┤
│ Task4 │ Task2 │ Task3 │ ← Task1完成後,
│ 執行中 │ 執行中 │ 執行中 │ Task4開始執行
└───────────┴───────────┴───────────┘
當設定並發數為3時,系統一次最多執行3個任務。當其中一個任務完成後,池中會立即從佇列取出下一個任務開始執行,保持並發數量恆定,直到所有任務都完成。
在前端面試中,實作一個簡單的 Promise Pool 是測試候選人對 Promise、非同步處理和執行控制理解的好題目。
基本實作:Promise Pool
以下是一個基本的 Promise Pool 實作,限制同時運行的 Promise 數量:
const promisePool = async function(functions, n) {
return new Promise((resolve) => {
let inProgress = 0; // 正在執行的 Promise 數量
let functionIndex = 0; // 待執行函數的索引
// 定義協助執行下一個函數的函數
function helper() {
// 如果所有函數都已完成,解析 Promise
if (functionIndex >= functions.length && inProgress === 0) {
resolve();
return;
}
// 當有空閒額度且還有函數待執行時,執行下一個
while (inProgress < n && functionIndex < functions.length) {
// 取得下一個函數
const fn = functions[functionIndex++];
inProgress++; // 增加執行中的 Promise 數量
// 執行函數
fn().then(() => {
inProgress--; // 減少執行中的 Promise 數量
helper(); // 嘗試執行下一個
});
}
}
// 開始執行
helper();
});
};
使用這個 Promise Pool 的例子:
// 模擬異步任務的函數
const createTask = (id, delay) => {
return () => new Promise(resolve => {
console.log(`任務 ${id} 開始執行,預計耗時 ${delay}ms`);
setTimeout(() => {
console.log(`任務 ${id} 完成`);
resolve(`任務 ${id} 的結果`);
}, delay);
});
};
// 創建一系列任務
const tasks = [
createTask(1, 1000),
createTask(2, 500),
createTask(3, 2000),
createTask(4, 800),
createTask(5, 1500),
createTask(6, 1000),
createTask(7, 600),
createTask(8, 3000)
];
// 使用 Promise Pool,最大並發數為 3
promisePool(tasks, 3).then(() => {
console.log('所有任務完成');
});
// 輸出(時間僅供參考):
// 任務 1 開始執行,預計耗時 1000ms
// 任務 2 開始執行,預計耗時 500ms
// 任務 3 開始執行,預計耗時 2000ms
// 任務 2 完成
// 任務 4 開始執行,預計耗時 800ms
// 任務 1 完成
// 任務 5 開始執行,預計耗時 1500ms
// 任務 4 完成
// 任務 6 開始執行,預計耗時 1000ms
// ...
// 所有任務完成
實際應用場景
Promise Pool 在前端開發中有很多實際應用場景:
1. 批量上傳檔案
// 批量上傳檔案示例
async function uploadFilesWithLimit(files, maxConcurrent) {
const uploadTasks = files.map(file => {
return async () => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Upload failed for ${file.name}`);
}
return await response.json();
};
});
return promisePoolWithResults(uploadTasks, maxConcurrent);
}
// 使用例子
document.getElementById('fileInput').addEventListener('change', async (event) => {
const files = event.target.files;
try {
const results = await uploadFilesWithLimit(Array.from(files), 3);
console.log('所有檔案上傳完成', results);
} catch (error) {
console.error('上傳過程發生錯誤', error);
}
});
2. 分頁資料批次處理
// 分頁資料批次處理示例
async function processAllPages(totalPages, maxConcurrent) {
// 創建頁面處理任務
const pageTasks = Array.from({ length: totalPages }, (_, i) => {
const page = i + 1;
return async () => {
console.log(`處理第 ${page} 頁`);
const response = await fetch(`/api/data?page=${page}`);
if (!response.ok) {
throw new Error(`Failed to fetch page ${page}`);
}
const data = await response.json();
// 進行資料處理...
return processPagesData(data);
};
});
// 使用 Promise Pool 限制並發
return promisePoolWithResults(pageTasks, maxConcurrent);
}
// 使用例子
processAllPages(100, 5)
.then(results => {
console.log('所有頁面處理完成');
// 合併或進一步處理結果...
})
.catch(error => {
console.error('處理過程發生錯誤', error);
});