鲁斯前端布鲁斯前端

文章中英模式

布鲁斯前端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);
  });