文章中英模式
布鲁斯前端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);
});