在这里插入图片描述

🤍 前端开发工程师、技术日更博主、已过CET6
🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1
🕠 牛客高级专题作者、打造专栏《前端面试必备》《2024面试高频手撕题》《前端求职突破计划》
🍚 蓝桥云课签约作者、上架课程《Vue.js 和 Egg.js 开发企业级健康管理项目》《带你从入门到实战全面掌握 uni-app》

在 JavaScript 中,Promise 是处理异步操作的核心工具。然而,当涉及到多个异步任务时,如何有效地控制并发数量是一个重要的问题。并发控制不仅可以提高性能,还可以避免因过多的并发请求导致的资源耗尽或性能瓶颈。本文将详细介绍 Promise 的几种并发控制方式,并探讨它们的实现和适用场景。

一、为什么需要并发控制

在处理多个异步任务时,如果不加以控制,可能会导致以下问题:

  1. 资源耗尽:过多的并发请求可能会耗尽系统资源,例如网络带宽、内存等。
  2. 性能瓶颈:过多的并发请求可能会导致服务器过载,从而降低响应速度。
  3. 错误处理复杂:如果某些请求失败,需要重新发起请求,过多的并发可能会使错误处理变得更加复杂。

因此,合理控制并发数量是异步编程中的一个重要环节。

二、Promise 的并发控制方法

(一)Promise.all

Promise.all 是最常用的并发控制方法之一。它接受一个 Promise 数组作为参数,当所有 Promise 都成功完成时,返回一个新的 Promise,其结果是一个包含所有 Promise 结果的数组。

示例代码
const promises = [
  fetch('https://api.example.com/data1').then(res => res.json()),
  fetch('https://api.example.com/data2').then(res => res.json()),
  fetch('https://api.example.com/data3').then(res => res.json())
];

Promise.all(promises)
  .then(results => {
    console.log(results); // [data1, data2, data3]
  })
  .catch(error => {
    console.error('One of the requests failed:', error);
  });
特点
  • 优点
    • 简单易用,适合同时处理多个独立的异步任务。
    • 所有任务并行执行,效率高。
  • 缺点
    • 如果任何一个 Promise 失败,整个 Promise.all 会立即失败,后续的 Promise 也会被中断。
    • 无法动态控制并发数量。

(二)Promise.allSettled

Promise.allSettledPromise.all 的一个变体,它不会因为某个 Promise 的失败而中断整个操作。它会等待所有 Promise 都完成(无论是成功还是失败),并返回一个包含每个 Promise 状态和结果的数组。

示例代码
const promises = [
  Promise.resolve(1),
  Promise.reject(new Error('Error')),
  Promise.resolve(3)
];

Promise.allSettled(promises)
  .then(results => {
    console.log(results);
    // [
    //   { status: "fulfilled", value: 1 },
    //   { status: "rejected", reason: Error: Error },
    //   { status: "fulfilled", value: 3 }
    // ]
  });
特点
  • 优点
    • 不会被任何一个 Promise 的失败中断,可以处理所有任务的结果。
  • 缺点
    • 无法动态控制并发数量。
    • 仍然无法提前终止失败的任务。

(三)Promise.race

Promise.race 用于处理多个异步任务,但它只会返回第一个完成的 Promise 的结果,其他 Promise 的结果会被忽略。

示例代码
const promises = [
  new Promise(resolve => setTimeout(() => resolve('First'), 1000)),
  new Promise(resolve => setTimeout(() => resolve('Second'), 500))
];

Promise.race(promises)
  .then(result => {
    console.log(result); // "Second"
  })
  .catch(error => {
    console.error(error);
  });
特点
  • 优点
    • 可以快速获取第一个完成的任务结果。
  • 缺点
    • 只处理第一个完成的任务,其他任务的结果会被忽略。
    • 不适合需要处理多个任务的场景。

(四)并发控制:async/await + Promise.all

如果需要同时发起多个异步任务,但又希望控制并发数量,可以结合 async/awaitPromise.all。这种方法可以动态控制并发数量,同时保持任务的并行执行。

示例代码
async function fetchUsersWithConcurrency(userIds, concurrency) {
  const results = [];
  const executing = [];

  for (const id of userIds) {
    const promise = fetchUser(id).then(user => {
      results.push(user);
    });

    executing.push(promise);

    if (executing.length >= concurrency) {
      await Promise.race(executing);
      executing.splice(executing.indexOf(Promise.race(executing)), 1);
    }
  }

  await Promise.all(executing);
  return results;
}

const userIds = [1, 2, 3, 4, 5];
fetchUsersWithConcurrency(userIds, 3).then(results => {
  console.log(results); // [user1, user2, user3, user4, user5]
});
特点
  • 优点
    • 可以动态控制并发数量,同时保持任务的并行执行。
    • 适合处理大量异步任务,避免因并发过多导致的性能问题。
  • 缺点
    • 实现相对复杂,需要手动管理并发任务。

(五)并发控制:p-limit

除了手动实现并发控制逻辑外,还可以使用第三方库来简化并发控制。p-limit 是一个流行的库,用于限制并发数量。

示例代码
const pLimit = require('p-limit');
const limit = pLimit(3);

const userIds = [1, 2, 3, 4, 5];
const promises = userIds.map(id => limit(() => fetchUser(id)));

Promise.all(promises)
  .then(results => {
    console.log(results); // [user1, user2, user3, user4, user5]
  })
  .catch(error => {
    console.error(error);
  });
特点
  • 优点
    • 简单易用,封装了并发控制逻辑。
    • 适合处理大量异步任务,避免因并发过多导致的性能问题。
  • 缺点
    • 需要引入第三方库。

三、选择合适的方法

在选择并发控制方法时,需要根据具体需求和场景进行选择:

  • Promise.all:适用于同时处理多个独立的异步任务,任务之间没有依赖关系。
  • Promise.allSettled:适用于需要处理所有任务的结果,即使某些任务失败。
  • Promise.race:适用于只需要第一个完成的任务结果。
  • async/await + Promise.all:适用于需要动态控制并发数量,同时保持任务并行执行。
  • p-limit:适用于需要简化并发控制逻辑,避免手动管理并发任务。

四、总结

并发控制是处理多个异步任务时的一个重要环节。通过合理使用 Promise.allPromise.allSettledPromise.raceasync/await 和第三方库(如 p-limit),可以高效地管理多个异步任务,并确保它们正确完成。

在实际开发中,开发者需要根据具体需求选择合适的方法。例如,如果任务之间没有依赖关系且需要快速完成,可以选择 Promise.all;如果需要处理所有任务的结果,可以选择 Promise.allSettled;如果需要动态控制并发数量,可以选择 async/await 结合并发控制逻辑或使用 p-limit

通过掌握这些方法,开发者可以更好地处理异步任务,提高代码的效率和可维护性。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐