在 JavaScript 的异步编程世界中,Promise 扮演着至关重要的角色。它允许我们以更优雅的方式处理异步操作,但默认情况下,Promise 并不提供并发限制。本文将深入探讨 Promise 的并发特性,解释为什么需要并发限制,并提供几种实现并发控制的方法。
Promise 的并发特性
立即执行
Promise 的一个显著特点是,一旦创建,其中的执行函数会立即被调用。这意味着,即使你稍后调用.then(),异步操作已经开始执行。例如:
const promise = new Promise((resolve) => {
  console.log('Executing');
  resolve();
});
// 输出: 'Executing'
无内置并发限制
当批量创建多个 Promise 时,所有任务会立即启动,不会限制同时进行的任务数。这可能导致资源的过度消耗,尤其是在大量并发请求时。
const tasks = Array.from({ length: 10 }, (_, i) =>
  new Promise((resolve) => {
    setTimeout(() => {
      console.log(`Task ${i} done`);
      resolve();
    }, 1000);
  })
);
Promise.all(tasks).then(() => console.log('All tasks done'));
为何需要并发限制?
- 「资源限制」: 系统资源(如网络请求、文件句柄、内存)是有限的。同时运行过多任务可能导致性能下降,甚至任务失败。 
- 「API 限制」:某些第三方 API 或服务可能对请求频率或并发量有限制。 
- 「用户体验」:在前端应用中,过多的并发操作可能会影响页面流畅性或加载速度。 
如何实现 Promise 并发限制?
1:使用手动队列管理
通过维护一个任务队列和一个计数器,可以控制同时执行的任务数量。以下是一个简单的实现:
function promiseWithConcurrencyLimit(tasks, limit) {
  const results = [];
  let activeCount = 0;
  let index = 0;
  return new Promise((resolve, reject) => {
    function next() {
      if (index === tasks.length && activeCount === 0) {
        return resolve(results);
      }
      while (activeCount < limit && index < tasks.length) {
        const taskIndex = index++;
        const task = tasks[taskIndex];
        activeCount++;
        task().then((result) => {
          results[taskIndex] = result;
        }).catch(reject).finally(() => {
          activeCount--;
          next();
        });
      }
    }
    next();
  });
}
2:使用第三方库
如果不想手动实现,可以使用成熟的第三方库,如p-limit。它提供了简单易用的 API 来限制并发。
const pLimit = require('p-limit');
const limit = pLimit(3); // 限制并发为 3
const tasks = Array.from({ length: 10 }, (_, i) => () =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(`Task ${i} done`);
      resolve(i);
    }, 1000)
  )
);
Promise.all(tasks.map((task) => limit(task))).then((results) => {
  console.log('All tasks done:', results);
});
3:利用异步生成器
通过异步生成器(async/await)和控制并发量的逻辑,可以实现更直观的代码流:
async function promiseWithConcurrencyLimit(tasks, limit) {
  const results = [];
  const executing = [];
  for (const task of tasks) {
    const promise = task();
    results.push(promise);
    executing.push(promise);
    if (executing.length >= limit) {
      await Promise.race(executing);
      executing.splice(executing.findIndex((p) => p === promise), 1);
    }
  }
  await Promise.all(executing);
  return Promise.all(results);
}
总结
- 「Promise 本身无并发限制」,但通过队列、第三方库或异步生成器等方法,可以灵活地实现并发控制。
- 「选择适合的实现方式」:对于简单场景可以手动实现队列管理,而复杂项目中则推荐使用第三方库。
- 「最佳实践」:根据实际场景合理设置并发限制,以平衡资源利用和性能。
希望本文能帮助你理解 Promise 的并发控制及其重要性!
该文章在 2024/12/5 14:43:03 编辑过