前置知识: JavaScript

异步并发控制

1 minAdvanced2026/6/14

JavaScript异步并发控制:p-limit模式、队列实现与并发限制策略。

1. 并发控制问题

1.1 无限制并发的风险

//  同时发起 1000 个请求
const urls = Array.from({ length: 1000 }, (_, i) => `/api/item/${i}`);
await Promise.all(urls.map((url) => fetch(url)));
// 可能导致:
// - 浏览器连接数耗尽(Chrome 同域最多 6 个)
// - 服务器拒绝服务(429 Too Many Requests)
// - 内存溢出

1.2 并发控制目标

  • 限制同时执行的异步操作数量
  • 保持最大并发度以提高吞吐量
  • 任务完成后自动拉入下一个任务

2. p-limit 模式

2.1 基本实现

function pLimit(concurrency) {
  let activeCount = 0;
  const queue = [];

  const next = () => {
    activeCount--;
    if (queue.length > 0) {
      queue.shift()();
    }
  };

  const run = async (fn, resolve, reject) => {
    activeCount++;
    try {
      const result = await fn();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      next();
    }
  };

  const enqueue = (fn) => {
    return new Promise((resolve, reject) => {
      if (activeCount < concurrency) {
        run(fn, resolve, reject);
      } else {
        queue.push(() => run(fn, resolve, reject));
      }
    });
  };

  return enqueue;
}

2.2 使用方式

const limit = pLimit(3); // 最多 3 个并发

const results = await Promise.all([
  limit(() => fetch('/api/1')),
  limit(() => fetch('/api/2')),
  limit(() => fetch('/api/3')),
  limit(() => fetch('/api/4')), // 等待前面的完成
  limit(() => fetch('/api/5')),
]);

2.3 动态并发度

const limit = pLimit(navigator.connection?.effectiveType === '4g' ? 6 : 3);

3. 异步队列实现

3.1 完整的任务队列

class AsyncQueue {
  constructor(concurrency = 4) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  push(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.run();
    });
  }

  run() {
    while (this.running < this.concurrency && this.queue.length > 0) {
      const { task, resolve, reject } = this.queue.shift();
      this.running++;

      task()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--;
          this.run();
        });
    }
  }

  get size() {
    return this.queue.length;
  }

  get pending() {
    return this.running;
  }
}

3.2 优先级队列

class PriorityAsyncQueue {
  constructor(concurrency = 4) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queues = {
      high: [],
      normal: [],
      low: [],
    };
  }

  push(task, priority = 'normal') {
    return new Promise((resolve, reject) => {
      this.queues[priority].push({ task, resolve, reject });
      this.run();
    });
  }

  getNext() {
    for (const priority of ['high', 'normal', 'low']) {
      if (this.queues[priority].length > 0) {
        return this.queues[priority].shift();
      }
    }
    return null;
  }

  run() {
    while (this.running < this.concurrency) {
      const item = this.getNext();
      if (!item) break;

      this.running++;
      const { task, resolve, reject } = item;

      task()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--;
          this.run();
        });
    }
  }
}

3.3 可取消队列

class CancellableAsyncQueue {
  constructor(concurrency = 4) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = new Map(); // id → task
    this.nextId = 0;
  }

  push(task) {
    const id = this.nextId++;
    return {
      id,
      promise: new Promise((resolve, reject) => {
        this.queue.set(id, { task, resolve, reject });
        this.run();
      }),
    };
  }

  cancel(id) {
    const item = this.queue.get(id);
    if (item) {
      this.queue.delete(id);
      item.reject(new Error('Cancelled'));
      return true;
    }
    return false;
  }

  cancelAll() {
    for (const [id, item] of this.queue) {
      item.reject(new Error('Cancelled'));
      this.queue.delete(id);
    }
  }

  run() {
    while (this.running < this.concurrency && this.queue.size > 0) {
      const [id, { task, resolve, reject }] = this.queue.entries().next().value;
      this.queue.delete(id);
      this.running++;

      task()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--;
          this.run();
        });
    }
  }
}

4. 批处理模式

4.1 分批执行

async function batchProcess(items, batchSize, processFn) {
  const results = [];

  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map(processFn));
    results.push(...batchResults);
  }

  return results;
}

// 使用
const allResults = await batchProcess(urls, 10, (url) => fetch(url));

4.2 滑动窗口

async function slidingWindow(items, concurrency, processFn) {
  const results = new Array(items.length);
  let nextIndex = 0;

  async function worker() {
    while (nextIndex < items.length) {
      const index = nextIndex++;
      results[index] = await processFn(items[index], index);
    }
  }

  await Promise.all(Array.from({ length: concurrency }, () => worker()));
  return results;
}

5. 实际应用

5.1 文件上传

class Uploader {
  constructor(maxConcurrent = 3) {
    this.queue = new AsyncQueue(maxConcurrent);
  }

  async upload(file) {
    return this.queue.push(async () => {
      const formData = new FormData();
      formData.append('file', file);
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData,
      });
      return response.json();
    });
  }
}

5.2 爬虫限速

class RateLimitedFetcher {
  constructor(requestsPerSecond = 5) {
    this.interval = 1000 / requestsPerSecond;
    this.lastRequestTime = 0;
  }

  async fetch(url) {
    const now = Date.now();
    const elapsed = now - this.lastRequestTime;
    const delay = Math.max(0, this.interval - elapsed);

    if (delay > 0) {
      await new Promise((r) => setTimeout(r, delay));
    }

    this.lastRequestTime = Date.now();
    return fetch(url);
  }
}