Promise.allSettled:获取所有 Promise 的状态结果

Promise.allSettled:当“完美”不可期,我们该如何优雅谢幕?

在人生的舞台上,我们总会遇到各种各样的承诺,大到“我要改变世界”,小到“我明天准时还你钱”。这些承诺就像一个个Promise,承载着我们的期望,也可能带来失望。在JavaScript的世界里,Promise也是同样的角色,它们代表着异步操作的最终结果,成功了皆大欢喜,失败了也得有个说法。

想象一下,你是一个乐队的经纪人,雄心勃勃地安排了一场全国巡演。你给每个城市的演出场馆都发出了预定请求,每一个请求都像一个Promise,代表着一个城市演出的成功与否。如果一切顺利,你的巡演计划就能完美执行,乐队也能名利双收。

但是,人生不如意十之八九。也许某个城市因为不可抗力取消了演出,也许某个场馆临时出了故障。如果其中一个Promise失败了,你难道要取消整个巡演吗?乐队成员怕是要跟你拼命!

这个时候,Promise.allSettled就派上用场了。它就像一个经验丰富的巡演总监,即使有些城市出了问题,也能确保整个巡演继续进行,并且还能给你一份详细的报告,告诉你哪些城市成功了,哪些城市失败了,以及失败的原因。

什么是Promise.allSettled?

简单来说,Promise.allSettled是一个静态方法,它接收一个Promise数组作为参数,并返回一个新的Promise。这个新的Promise会在所有输入的Promise都完成(无论成功还是失败)后resolve。

Promise.all不同的是,Promise.allSettled不会因为其中一个Promise的失败而立即reject。它会等待所有Promise都完成,然后返回一个包含每个Promise结果的数组。

Promise.allSettled的返回值

Promise.allSettled返回的数组中,每个元素都是一个对象,包含以下两个属性:

  • status: 字符串,表示Promise的结果状态,可以是"fulfilled" (成功) 或 "rejected" (失败)。
  • value: 如果Promise成功,则包含Promise的resolve值。
  • reason: 如果Promise失败,则包含Promise的reject原因。

一个简单的例子

让我们回到乐队巡演的例子。假设我们有三个Promise,分别代表三个城市的演出预定:

const city1 = Promise.resolve("北京演出预定成功!");
const city2 = Promise.reject("上海演出场馆故障,预定失败!");
const city3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("广州演出预定成功!");
  }, 1000); // 模拟异步操作
});

Promise.allSettled([city1, city2, city3])
  .then((results) => {
    console.log(results);
    // 输出结果:
    // [
    //   { status: 'fulfilled', value: '北京演出预定成功!' },
    //   { status: 'rejected', reason: '上海演出场馆故障,预定失败!' },
    //   { status: 'fulfilled', value: '广州演出预定成功!' }
    // ]

    //  可以根据results分析巡演情况,制定应对策略
    let successfulCities = results.filter(result => result.status === 'fulfilled').map(result => result.value);
    let failedCities = results.filter(result => result.status === 'rejected').map(result => result.reason);

    console.log("成功预定的城市:", successfulCities); // 输出:成功预定的城市: [ '北京演出预定成功!', '广州演出预定成功!' ]
    console.log("预定失败的城市:", failedCities); // 输出:预定失败的城市: [ '上海演出场馆故障,预定失败!' ]
  });

在这个例子中,即使city2的Promise失败了,Promise.allSettled仍然等待所有Promise完成,并返回一个包含所有结果的数组。我们可以通过分析这个数组,了解每个城市的预定情况,并据此制定相应的策略。比如,我们可以联系上海的其他场馆,或者调整巡演计划,将上海的演出安排到其他时间。

Promise.allSettled的应用场景

Promise.allSettled在以下场景中非常有用:

  • 需要知道所有Promise的结果,即使其中一些Promise失败了。 比如,在发送多个请求时,我们可能需要知道哪些请求成功了,哪些请求失败了,以便进行错误处理和重试。
  • 需要并行执行多个独立的异步操作,并且不希望其中一个操作的失败影响其他操作。 比如,在加载多个资源时,我们不希望因为一个资源加载失败而阻止其他资源的加载。
  • 需要对多个Promise的结果进行统一处理。 比如,在处理用户提交的多个表单时,我们可以使用Promise.allSettled来等待所有表单验证完成,然后根据验证结果进行相应的操作。

Promise.allSettled vs Promise.all vs Promise.race vs Promise.any

JavaScript中还有其他几个处理Promise数组的方法,它们分别是Promise.allPromise.racePromise.any。让我们来对比一下它们之间的区别:

  • Promise.all 如果所有Promise都成功,则resolve一个包含所有resolve值的数组。如果其中任何一个Promise失败,则立即reject,并返回第一个reject原因。就像追求完美主义的艺术家,任何瑕疵都无法容忍。
  • Promise.race 返回第一个resolve或reject的Promise的结果。就像赛跑一样,谁先到达终点,就以谁的结果为准。
  • Promise.any 返回第一个resolve的Promise的结果。如果所有Promise都reject,则reject一个AggregateError,包含所有reject原因。就像大海捞针,只要找到一根,就算成功。
方法 成功条件 失败条件 返回值
Promise.all 所有Promise都resolve 任何一个Promise reject 包含所有resolve值的数组
Promise.allSettled 所有Promise都完成(无论resolve或reject) 包含所有Promise结果的数组,每个结果包含statusvaluereason属性
Promise.race 任何一个Promise resolve或reject 第一个resolve或reject的Promise的结果
Promise.any 任何一个Promise resolve 所有Promise都reject 第一个resolve的Promise的结果。如果所有Promise都reject,则reject一个AggregateError,包含所有reject原因

选择哪个方法取决于你的具体需求。如果你需要确保所有Promise都成功,并且希望在任何一个Promise失败时立即停止,那么Promise.all是最好的选择。如果你只需要知道第一个完成的Promise的结果,那么Promise.race是最好的选择。如果你只需要知道是否有任何一个Promise成功,那么Promise.any是最好的选择。而如果你需要知道所有Promise的结果,无论成功还是失败,那么Promise.allSettled就是你的不二之选。

Promise.allSettled的兼容性

Promise.allSettled是ES2020引入的新特性。这意味着一些旧版本的浏览器可能不支持它。如果你需要在旧版本的浏览器中使用Promise.allSettled,可以使用polyfill。

一个更复杂的例子:用户注册流程

让我们来看一个更复杂的例子,模拟一个用户注册流程。在这个流程中,我们需要执行以下几个步骤:

  1. 验证用户名是否可用。
  2. 验证邮箱是否可用。
  3. 创建用户账户。
  4. 发送欢迎邮件。

每个步骤都是一个异步操作,并且可能会失败。我们希望在所有步骤都完成后,向用户显示注册结果,无论成功还是失败。

async function registerUser(username, email, password) {
  const validateUsername = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (username === "admin") {
          reject("用户名已被占用!");
        } else {
          resolve("用户名可用。");
        }
      }, 500); // 模拟网络延迟
    });
  };

  const validateEmail = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (email === "[email protected]") {
          reject("邮箱已被注册!");
        } else {
          resolve("邮箱可用。");
        }
      }, 800); // 模拟网络延迟
    });
  };

  const createUserAccount = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // 假设创建账户失败
        // reject("创建账户失败!");
        resolve("账户创建成功!");
      }, 1200); // 模拟网络延迟
    });
  };

  const sendWelcomeEmail = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // 假设发送邮件失败
        // reject("发送欢迎邮件失败!");
        resolve("欢迎邮件发送成功!");
      }, 1500); // 模拟网络延迟
    });
  };

  const promises = [
    validateUsername(),
    validateEmail(),
    createUserAccount(),
    sendWelcomeEmail(),
  ];

  const results = await Promise.allSettled(promises);

  console.log("注册结果:", results);

  // 根据结果显示注册状态
  let successCount = 0;
  let failureMessages = [];

  results.forEach((result) => {
    if (result.status === "fulfilled") {
      successCount++;
      console.log(result.value); // 打印成功信息
    } else {
      failureMessages.push(result.reason);
      console.error(result.reason); // 打印错误信息
    }
  });

  if (failureMessages.length === 0) {
    console.log("恭喜你,注册成功!");
  } else {
    console.log("注册失败,请检查以下错误:");
    failureMessages.forEach((message) => console.log("- " + message));
  }
}

registerUser("newUser", "[email protected]", "password123");

在这个例子中,我们使用Promise.allSettled来等待所有注册步骤完成。然后,我们根据结果显示注册状态,并向用户提供详细的错误信息。即使某些步骤失败了,用户仍然可以知道哪些步骤成功了,哪些步骤失败了,以及失败的原因。这可以帮助用户更快地解决问题,并提高用户体验。

总结

Promise.allSettled是一个强大的工具,可以帮助我们处理复杂的异步操作,即使其中一些操作失败了,也能保证程序的稳定性和可靠性。它就像一个成熟稳重的朋友,即使在你遇到挫折时,也能给你支持和鼓励,并帮助你找到前进的方向。

希望这篇文章能帮助你更好地理解Promise.allSettled的用法和应用场景。下次当你需要处理多个Promise时,不妨试试Promise.allSettled,它可能会给你带来意想不到的惊喜!记住,人生就像一场巡演,总会有一些不如意,但只要我们坚持下去,总会找到属于自己的舞台。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注