异步并发控制pLimit 实现总结

异步并发控制pLimit 实现总结

一、核心概念

1. 什么是异步并发控制?

同时发起大量异步任务(接口请求、文件读取、数据库查询)时,限制同时执行的任务数量,避免:

  • 浏览器 / 服务器崩溃、请求超时、接口限流

  • 资源占用过高、程序卡死

  • 网络拥堵、请求失败率飙升

2. 适用场景

  • 批量上传 / 下载文件

  • 批量请求接口

  • 批量数据库操作

  • 爬虫、批量数据处理


二、核心原理

  1. 维护一个任务队列,存放等待执行的异步任务

  2. 维护一个当前正在执行的任务计数器

  3. 每次执行任务时:

    • 计数器 < 并发数 → 立即执行

    • 计数器 ≥ 并发数 → 放入队列等待

  4. 一个任务执行完成后:

    • 计数器 -1

    • 从队列取出一个任务继续执行


三、手写实现:pLimit 函数(最标准版本)

/**
 * 异步并发控制器
 * @param {number} concurrency - 最大并发数
 * @returns {Function} limit - 包装异步任务的函数
 */
function pLimit(concurrency) {
  // 校验并发数
  if (!Number.isInteger(concurrency) || concurrency <= 0) {
    throw new TypeError("请传入大于 0 的整数");
  }

  // 等待队列:存放未执行的任务
  const queue = [];
  // 当前正在执行的任务数量
  let activeCount = 0;

  // 执行下一个任务
  const next = () => {
    activeCount--;

    // 队列有任务,取出执行
    if (queue.length > 0) {
      queue.shift()();
    }
  };

  // 执行异步任务
  const run = async (fn, resolve, args) => {
    // 正在执行的任务 +1
    activeCount++;

    // 执行任务
    try {
      const result = await fn(...args);
      resolve(result);
    } catch (err) {
      resolve(Promise.reject(err));
    }

    // 执行完,执行下一个
    next();
  };

  // 队列添加任务
  const enqueue = (fn, resolve, args) => {
    // 把执行函数放进队列
    queue.push(run.bind(null, fn, resolve, args));

    // 没达到最大并发,立即执行
    if (activeCount < concurrency) {
      queue.shift()();
    }
  };

  // 生成器:包装异步任务
  const limit = (fn, ...args) => {
    return new Promise((resolve) => {
      enqueue(fn, resolve, args);
    });
  };

  // 挂载状态(方便调试/扩展)
  Object.defineProperties(limit, {
    activeCount: { get: () => activeCount },
    queueSize: { get: () => queue.length },
  });

  return limit;
}

四、使用示例

// 1. 创建并发控制器:最多同时执行 2 个任务
const limit = pLimit(2);

// 2. 模拟异步任务
async function task(id) {
  console.log(`任务 ${id} 开始执行`);
  await new Promise((r) => setTimeout(r, 1000));
  console.log(`任务 ${id} 执行完成`);
  return id;
}

// 3. 批量添加任务(只会同时跑 2 个)
const list = [1, 2, 3, 4, 5, 6];
const promises = list.map((item) => limit(task, item));

// 4. 等待全部完成
Promise.all(promises).then(console.log);

执行效果

任务1 开始执行
任务2 开始执行
(1秒后)
任务1 完成
任务2 完成
任务3 开始执行
任务4 开始执行
...

永远不会同时执行超过 2 个任务


五、常用工具库(真实项目)

1. tiny-async-pool(轻量)

npm i tiny-async-pool

2. p-limit(最流行、标准)

npm i p-limit

用法和我们手写的完全一致。


六、面试考点

1. 为什么要做并发控制?

  • 防止请求泛滥导致服务崩溃

  • 遵守接口限流规则

  • 提高稳定性、成功率

2. pLimit 核心靠什么实现?

  • 队列 + 计数器

  • 任务完成后自动消费下一个

  • Promise 包装异步任务

3. 手写 pLimit 关键点

  1. 参数校验

  2. 任务队列管理

  3. 正在执行计数器

  4. 任务完成自动调度

  5. 异常捕获不阻塞流程


七、一句话总结

异步并发控制 = 限制同时跑的异步任务数量,用队列 + 计数器实现,是批量异步任务的必备优化手段,面试高频手写题

需要我再给你做一版超级精简面试背诵版吗?只有 10 行核心代码那种。

(注:文档部分内容可能由 AI 生成)


本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。

萧兮的博客https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/019e4ece-bf14-70c3-82f3-286b8513fac9