在这里插入图片描述

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

在现代 JavaScript 开发中,异步编程是处理 I/O 操作(如网络请求、文件读写等)的常见方式。然而,当需要同时发起多个异步任务时,如何高效地管理这些任务并确保它们正确完成,是一个常见的挑战。本文将详细介绍如何同时发起多个异步任务,并探讨不同的实现方法及其适用场景。

一、异步任务的常见场景

在实际开发中,我们经常需要同时处理多个异步任务。以下是一些常见的场景:

  1. 并发请求:同时向多个 API 发起请求,获取数据后进行后续处理。
  2. 并行处理:对多个数据项进行异步处理,例如批量上传文件或批量处理数据。
  3. 任务组合:多个异步任务相互依赖,需要在所有任务完成后执行后续操作。

二、实现方法

(一)Promise.all

Promise.all 是处理多个异步任务的最常用方法之一。它接受一个 Promise 数组作为参数,当所有 Promise 都成功完成时,返回一个新的 Promise,其结果是一个包含所有 Promise 结果的数组。

示例代码
function fetchUser(id) {
  return fetch(`https://api.example.com/users/${id}`).then((res) => res.json());
}

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

Promise.all(promises)
  .then((results) => {
    console.log(results); // [user1, user2, user3]
  })
  .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 + for...of 循环

如果需要动态控制并发数量,可以结合 async/awaitfor...of 循环逐个处理异步任务。这种方法可以更好地控制任务的执行顺序和并发数量。

示例代码
async function fetchUsers(userIds) {
  const results = [];
  for (const id of userIds) {
    try {
      const user = await fetchUser(id);
      results.push(user);
    } catch (error) {
      console.error(`Failed to fetch user ${id}:`, error);
    }
  }
  return results;
}

const userIds = [1, 2, 3];
fetchUsers(userIds).then((results) => {
  console.log(results); // [user1, user2, user3]
});
特点
  • 优点
    • 可以动态控制任务的执行顺序和并发数量。
    • 可以逐个处理失败的任务,不会中断整个操作。
  • 缺点
    • 任务是顺序执行的,效率较低。

(五)并发控制: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]
});
特点
  • 优点
    • 可以动态控制并发数量,同时保持任务的并行执行。
    • 适合处理大量异步任务,避免因并发过多导致的性能问题。
  • 缺点
    • 实现相对复杂,需要手动管理并发任务。

三、选择合适的方法

在选择同时发起多个异步任务的方法时,需要根据具体需求和场景进行选择:

  • Promise.all:适用于同时处理多个独立的异步任务,任务之间没有依赖关系。
  • Promise.allSettled:适用于需要处理所有任务的结果,即使某些任务失败。
  • Promise.race:适用于只需要第一个完成的任务结果。
  • async/await + for...of 循环:适用于需要动态控制任务执行顺序和并发数量。
  • 并发控制:async/await + Promise.all:适用于需要控制并发数量,同时保持任务并行执行。

四、总结

同时发起多个异步任务是现代 JavaScript 开发中的常见需求。通过合理使用 Promise.allPromise.allSettledPromise.raceasync/await 和并发控制方法,可以高效地管理多个异步任务,并确保它们正确完成。

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

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

Logo

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

更多推荐