JS `Promise.any` (ES2021):获取第一个成功解决的 Promise

各位观众,早上好/下午好/晚上好!我是你们今天的 Promise 导师,很高兴能在这里和大家一起聊聊 JavaScript 中一个比较新的 Promise 方法:Promise.any

别害怕,虽然听起来像是什么高端武器,但实际上 Promise.any 的用法非常简单粗暴,它就像一个“谁先到谁先得”的比赛裁判,专门负责从一堆 Promise 中挑选出第一个成功解决 (resolved) 的那个。如果所有的 Promise 都失败了 (rejected),它才会告诉你“没人赢!”。

那么,接下来就让我们一起深入了解一下这个有趣的方法吧!

1. 认识 Promise.any:一个“胜者为王”的 Promise 方法

Promise.any 是 ES2021 引入的一个新的 Promise 方法,它的主要作用是接收一个可迭代对象(比如数组),该对象包含多个 Promise 实例。Promise.any 会等待这些 Promise 中的任何一个成功解决,一旦有 Promise 成功解决,它就会立即返回一个已经解决的 Promise,其值为第一个成功解决的 Promise 的值。

简单来说,Promise.any 就像是在赛跑,哪个 Promise 先跑到终点(成功解决),就取它的结果作为最终结果。

2. Promise.any 的基本语法

Promise.any 的语法非常简单:

Promise.any(iterable);

其中 iterable 是一个可迭代对象,通常是一个包含 Promise 实例的数组。

返回值:

  • 如果 iterable 中至少有一个 Promise 成功解决,Promise.any 会返回一个已经解决的 Promise,其值为第一个成功解决的 Promise 的值。
  • 如果 iterable 中所有的 Promise 都失败了,Promise.any 会返回一个已经拒绝的 Promise,其值为一个 AggregateError 实例。AggregateError 是一个包含了所有被拒绝的 Promise 的错误的数组。

3. Promise.any 的使用场景:从多个数据源获取数据

Promise.any 最常见的应用场景是从多个数据源获取数据,例如从多个 CDN 加载资源,或者从多个 API 获取数据。

假设我们需要从三个不同的 API 获取用户信息,但是我们并不关心具体使用哪个 API,只要能获取到数据就行。我们可以使用 Promise.any 来实现这个需求:

function getUserInfoFromAPI1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.2; // 模拟API成功或失败
      if (success) {
        resolve({ id: 1, name: "Alice (API1)" });
      } else {
        reject(new Error("API1 failed"));
      }
    }, 500);
  });
}

function getUserInfoFromAPI2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.5; // 模拟API成功或失败
      if (success) {
        resolve({ id: 2, name: "Bob (API2)" });
      } else {
        reject(new Error("API2 failed"));
      }
    }, 200);
  });
}

function getUserInfoFromAPI3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.8; // 模拟API成功或失败
      if (success) {
        resolve({ id: 3, name: "Charlie (API3)" });
      } else {
        reject(new Error("API3 failed"));
      }
    }, 1000);
  });
}

Promise.any([
  getUserInfoFromAPI1(),
  getUserInfoFromAPI2(),
  getUserInfoFromAPI3(),
])
  .then((user) => {
    console.log("成功获取到用户信息:", user);
  })
  .catch((error) => {
    console.error("所有 API 都失败了:", error);
  });

在这个例子中,Promise.any 会等待 getUserInfoFromAPI1getUserInfoFromAPI2getUserInfoFromAPI3 这三个 Promise 中的任何一个成功解决。如果 getUserInfoFromAPI2 最先成功解决,那么 Promise.any 就会返回一个已经解决的 Promise,其值为 getUserInfoFromAPI2 返回的用户信息。如果所有的 API 都失败了,Promise.any 就会返回一个已经被拒绝的 Promise,其值为一个 AggregateError 实例,其中包含了所有 API 返回的错误。

4. Promise.anyPromise.race 的区别:细微但重要的差异

Promise.anyPromise.race 都是用于处理多个 Promise 的方法,但它们之间存在一些重要的区别。

  • Promise.any 只要有一个 Promise 成功解决,就返回该 Promise 的值。只有当所有 Promise 都失败时,才会返回一个 AggregateError。它关注的是“成功”,只要有一个成功就OK。
  • Promise.race 只要有一个 Promise 完成(无论是成功解决还是失败拒绝),就返回该 Promise 的值。它关注的是“速度”,哪个 Promise 最先完成就取哪个。

可以用表格来总结一下:

特性 Promise.any Promise.race
成功条件 至少有一个 Promise 成功解决 任何一个 Promise 完成(成功或失败)
失败条件 所有 Promise 都失败 N/A (只要有一个 Promise 完成,就返回结果)
返回值 第一个成功解决的 Promise 的值,或 AggregateError 第一个完成的 Promise 的值(成功或失败)

从上面的表格可以看出,Promise.any 更关注的是结果的“正确性”,只要能得到一个正确的结果就行。而 Promise.race 更关注的是“速度”,哪个 Promise 最先返回结果就取哪个,即使这个结果是错误的。

举个例子:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => reject("Promise 1 rejected"), 100);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Promise 2 resolved"), 200);
});

// Promise.any
Promise.any([promise1, promise2])
  .then((value) => console.log("Promise.any 成功:", value)) // 输出: Promise.any 成功: Promise 2 resolved
  .catch((error) => console.error("Promise.any 失败:", error));

// Promise.race
Promise.race([promise1, promise2])
  .then((value) => console.log("Promise.race 成功:", value))
  .catch((error) => console.error("Promise.race 失败:", error)); // 输出: Promise.race 失败: Promise 1 rejected

在这个例子中,promise1 在 100 毫秒后被拒绝,promise2 在 200 毫秒后被解决。

  • Promise.any 会等待 promise2 解决,然后返回 "Promise 2 resolved"。
  • Promise.race 会在 promise1 被拒绝后立即返回,并抛出 "Promise 1 rejected" 错误。

5. Promise.anyPromise.all 的区别:一个“或”与“且”的选择

Promise.anyPromise.all 是两个截然不同的 Promise 方法,它们分别代表了“或”和“且”的关系。

  • Promise.any 只要有一个 Promise 成功解决,就返回成功。
  • Promise.all 必须所有 Promise 都成功解决,才会返回成功。只要有一个 Promise 失败,就返回失败。

可以用表格来总结一下:

特性 Promise.any Promise.all
成功条件 至少有一个 Promise 成功解决 所有 Promise 都成功解决
失败条件 所有 Promise 都失败 只要有一个 Promise 失败
返回值 第一个成功解决的 Promise 的值,或 AggregateError 包含所有 Promise 结果的数组,或第一个失败的 Promise 的错误

举个例子:

const promise1 = Promise.resolve("Promise 1 resolved");
const promise2 = Promise.reject("Promise 2 rejected");
const promise3 = Promise.resolve("Promise 3 resolved");

// Promise.any
Promise.any([promise1, promise2, promise3])
  .then((value) => console.log("Promise.any 成功:", value)) // 输出: Promise.any 成功: Promise 1 resolved
  .catch((error) => console.error("Promise.any 失败:", error));

// Promise.all
Promise.all([promise1, promise2, promise3])
  .then((value) => console.log("Promise.all 成功:", value))
  .catch((error) => console.error("Promise.all 失败:", error)); // 输出: Promise.all 失败: Promise 2 rejected

在这个例子中,promise1promise3 成功解决,promise2 被拒绝。

  • Promise.any 会忽略 promise2 的失败,并返回 promise1 的值 "Promise 1 resolved"。
  • Promise.all 会因为 promise2 的失败而失败,并抛出 "Promise 2 rejected" 错误。

6. AggregateError:当所有 Promise 都失败时

Promise.any 接收到的所有 Promise 都失败时,它会返回一个已经被拒绝的 Promise,其值为一个 AggregateError 实例。

AggregateError 是一个包含了所有被拒绝的 Promise 的错误的数组。它提供了一个 errors 属性,该属性是一个包含了所有错误的数组。

const promise1 = Promise.reject("Promise 1 rejected");
const promise2 = Promise.reject("Promise 2 rejected");
const promise3 = Promise.reject("Promise 3 rejected");

Promise.any([promise1, promise2, promise3])
  .then((value) => console.log("成功:", value))
  .catch((error) => {
    console.error("所有 Promise 都失败了:", error);
    console.log("错误信息:", error.errors); // 输出: 错误信息: ["Promise 1 rejected", "Promise 2 rejected", "Promise 3 rejected"]
  });

在这个例子中,所有的 Promise 都被拒绝,因此 Promise.any 会返回一个 AggregateError 实例。我们可以通过 error.errors 属性来访问所有被拒绝的 Promise 的错误信息。

7. Promise.any 的兼容性:并非所有浏览器都支持

Promise.any 是 ES2021 引入的新特性,因此并非所有浏览器都支持。在使用 Promise.any 之前,最好先检查一下浏览器的兼容性。

可以使用 try...catch 语句来检测浏览器是否支持 Promise.any

try {
  if (Promise.any) {
    console.log("浏览器支持 Promise.any");
  } else {
    console.log("浏览器不支持 Promise.any");
  }
} catch (error) {
  console.log("浏览器不支持 Promise.any");
}

如果浏览器不支持 Promise.any,可以使用 polyfill 来提供兼容性支持。例如,可以使用 core-js 这个库来 polyfill Promise.any

npm install core-js

然后在代码中引入 core-js

import 'core-js/features/promise/any';

// 现在就可以使用 Promise.any 了

8. Promise.any 的最佳实践:避免不必要的错误

在使用 Promise.any 时,需要注意以下几点:

  • 确保 iterable 中包含的是 Promise 实例: 如果 iterable 中包含的不是 Promise 实例,Promise.any 会将其转换为 Promise 实例。但这可能会导致一些意想不到的错误。
  • 处理 AggregateError 当所有 Promise 都失败时,Promise.any 会返回一个 AggregateError 实例。需要正确处理 AggregateError,避免程序崩溃。
  • 注意兼容性: Promise.any 是 ES2021 引入的新特性,需要注意浏览器的兼容性。

9. 总结:Promise.any 的价值和局限

Promise.any 是一个非常有用的 Promise 方法,它可以帮助我们从多个数据源获取数据,提高程序的健壮性和性能。

Promise.any 的价值:

  • 提高程序的健壮性: 当从多个数据源获取数据时,如果某个数据源出现故障,Promise.any 可以保证程序仍然能够正常运行。
  • 提高程序的性能: Promise.any 可以并行地从多个数据源获取数据,从而缩短程序的运行时间。
  • 简化代码: Promise.any 可以简化从多个数据源获取数据的代码,使代码更加简洁易懂。

Promise.any 的局限:

  • 兼容性问题: Promise.any 是 ES2021 引入的新特性,需要注意浏览器的兼容性。
  • 错误处理: 需要正确处理 AggregateError,避免程序崩溃。
  • 不适用于所有场景: Promise.any 只适用于从多个数据源获取数据,并且只需要获取一个成功结果的场景。

10. 最后的彩蛋:一个更复杂的例子

假设我们正在构建一个图片加载器,我们需要从多个 CDN 加载图片。为了提高加载速度,我们希望使用 Promise.any 来并行地从多个 CDN 加载图片。

function loadImageFromCDN(cdnUrl, imageUrl) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      console.log(`从 ${cdnUrl} 加载图片成功: ${imageUrl}`);
      resolve(img);
    };
    img.onerror = () => {
      console.error(`从 ${cdnUrl} 加载图片失败: ${imageUrl}`);
      reject(new Error(`加载图片失败: ${imageUrl} from ${cdnUrl}`));
    };
    img.src = `${cdnUrl}/${imageUrl}`;
  });
}

const cdnUrls = [
  "https://cdn1.example.com",
  "https://cdn2.example.com",
  "https://cdn3.example.com",
];

const imageUrl = "images/my-image.jpg";

const imagePromises = cdnUrls.map((cdnUrl) => loadImageFromCDN(cdnUrl, imageUrl));

Promise.any(imagePromises)
  .then((img) => {
    // 图片加载成功,将图片添加到页面
    document.body.appendChild(img);
  })
  .catch((error) => {
    console.error("所有 CDN 都加载图片失败:", error);
    // 显示默认图片或提示用户
  });

在这个例子中,我们使用 Promise.any 来并行地从多个 CDN 加载图片。只要有一个 CDN 成功加载了图片,我们就可以将图片添加到页面。如果所有的 CDN 都加载图片失败,我们就显示默认图片或提示用户。

好了,今天的讲座就到这里。希望大家通过今天的学习,对 Promise.any 有了更深入的了解。记住,熟练掌握 Promise.any,可以让你在处理多个异步操作时更加得心应手!

感谢大家的收听!下次再见!

发表回复

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