JS `Promise.any()` (ES2021):获取第一个成功解决的 Promise,忽略失败

好吧,各位观众,欢迎来到今天的“Promise.any():谁先到谁先得”专场讲座!我是你们今天的Promise向导,咱们今天就来好好聊聊ES2021新出的这个Promise.any(),看看它到底能干啥,又该怎么用。

开场白:Promise,你变了!

Promise这玩意儿,大家伙儿肯定都熟得不能再熟了。以前我们处理多个Promise的时候,要么用Promise.all(),要么用Promise.race()Promise.all()要求所有Promise都成功才行,一个失败就全体失败;Promise.race()呢,比的是速度,谁跑得快就算谁的,但不管是成功还是失败,只要第一个完成就直接结束。

但是!这两种方法都有点极端,对不对?有时候我们只想知道,在这一堆Promise里,有没有一个能成功就行,其他的失败了就失败了,无所谓。这时候,Promise.any()就闪亮登场了!

Promise.any():一个成功就足够!

Promise.any()就像一个乐观主义者,它会遍历你给它的一堆Promise,只要其中有一个Promise成功解决(resolved),它就立即返回这个成功的结果。如果所有的Promise都失败了(rejected),那它才会返回一个包含所有失败原因的AggregateError

简单来说,Promise.any()就是:

  • 成功至上: 只要有一个成功,就返回成功结果。
  • 失败无所谓: 即使其他都失败,只要有一个成功,就没问题。
  • 全军覆没才算输: 只有所有Promise都失败了,才会返回一个特殊的错误。

语法结构:就这么简单!

Promise.any()的语法超级简单:

Promise.any(iterable);

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

实战演练:代码说话!

光说不练假把式,咱们直接上代码,看看Promise.any()在实际应用中有多好用。

例子一:谁先加载完图片,就显示谁!

假设我们有三张图片,我们想先显示加载速度最快的那个:

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      console.log(`图片 ${url} 加载成功!`);
      resolve(img);
    };
    img.onerror = () => {
      console.error(`图片 ${url} 加载失败!`);
      reject(new Error(`Failed to load image: ${url}`));
    };
    img.src = url;
  });
}

const imageUrls = [
  "https://via.placeholder.com/150/ff0000", // 红色
  "https://via.placeholder.com/150/00ff00", // 绿色
  "https://via.placeholder.com/150/0000ff", // 蓝色
];

Promise.any(imageUrls.map(loadImage))
  .then(img => {
    document.body.appendChild(img);
    console.log("成功加载并显示了第一张图片!");
  })
  .catch(error => {
    console.error("所有图片加载都失败了!", error);
  });

在这个例子中,loadImage()函数返回一个Promise,表示加载一张图片。Promise.any()会等待这三个Promise中的任何一个成功解决,然后将加载好的图片添加到页面上。如果所有图片都加载失败了,就会执行catch块。

例子二:从多个API获取数据,哪个先返回用哪个!

假设我们需要从多个API获取用户数据,但我们只关心第一个返回结果的API:

function fetchUserData(url) {
  return fetch(url)
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return response.json();
    })
    .catch(error => {
      console.error(`API ${url} 请求失败!`, error);
      throw error; // 重新抛出错误,让 Promise.any() 知道这个 Promise 失败了
    });
}

const apiUrls = [
  "https://api.example.com/user1", // 假设这个API比较快
  "https://api.backup.com/user1", // 备用API
  "https://api.another.com/user1"  // 另一个备用API
];

Promise.any(apiUrls.map(fetchUserData))
  .then(userData => {
    console.log("成功获取到用户数据:", userData);
  })
  .catch(error => {
    console.error("所有API请求都失败了!", error);
  });

在这个例子中,fetchUserData()函数返回一个Promise,表示从一个API获取用户数据。Promise.any()会等待这三个Promise中的任何一个成功解决,然后使用获取到的用户数据。如果所有API请求都失败了,就会执行catch块。

例子三:处理超时,哪个先响应算哪个!

假设我们想设置一个超时时间,如果API在指定时间内没有响应,就认为它失败:

function fetchWithTimeout(url, timeout) {
  return Promise.race([
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
      }),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), timeout)
    )
  ]);
}

const apiUrls = [
  "https://api.example.com/data1",
  "https://api.backup.com/data1",
  "https://api.another.com/data1"
];

const timeout = 2000; // 2秒超时

Promise.any(apiUrls.map(url => fetchWithTimeout(url, timeout)))
  .then(data => {
    console.log("成功获取到数据:", data);
  })
  .catch(error => {
    console.error("所有请求都失败了!", error);
  });

在这个例子中,fetchWithTimeout()函数使用Promise.race()来创建一个带有超时的Promise。如果API在超时时间内没有响应,Promise.race()会返回一个 rejected Promise,从而让Promise.any()知道这个API请求失败了。

AggregateError:全军覆没的信号!

Promise.any()接收到的所有Promise都失败时,它会返回一个AggregateError对象。这个对象包含一个errors属性,它是一个包含所有失败原因的数组。

Promise.any([
  Promise.reject(new Error("Error 1")),
  Promise.reject(new Error("Error 2")),
  Promise.reject(new Error("Error 3"))
])
.then(() => {
  // 这永远不会执行
})
.catch(error => {
  if (error instanceof AggregateError) {
    console.log("所有 Promise 都失败了!");
    console.log("失败原因:", error.errors);
  } else {
    console.error("发生了其他错误!", error);
  }
});

在这个例子中,所有Promise都失败了,所以catch块会被执行,并且error对象是一个AggregateError实例,它的errors属性包含了三个错误对象。

Promise.any() vs Promise.race():一字之差,天壤之别!

Promise.any()Promise.race()都是用于处理多个Promise的,但它们的行为却截然不同。

特性 Promise.any() Promise.race()
成功条件 只要有一个Promise成功解决 只要有一个Promise完成(成功或失败)
失败条件 所有Promise都失败 只要有一个Promise失败
返回值 第一个成功解决的Promise的结果 第一个完成的Promise的结果(成功或失败)
错误处理 如果所有Promise都失败,返回AggregateError 如果第一个完成的Promise失败,直接返回失败原因
应用场景 多个备选方案,只要有一个成功即可 竞争条件,只需要最快的结果,不在乎成功或失败

Promise.any()的兼容性:不是所有浏览器都支持!

Promise.any()是ES2021的新特性,所以并不是所有浏览器都支持。在使用之前,最好检查一下浏览器的兼容性。可以通过MDN Web Docs查看最新的兼容性信息。

如果需要在旧版本的浏览器中使用Promise.any(),可以使用polyfill。

Polyfill:自己动手,丰衣足食!

Polyfill是一种代码,它可以为旧版本的浏览器提供新特性的支持。下面是一个简单的Promise.any()的polyfill:

if (!Promise.any) {
  Promise.any = function(promises) {
    return new Promise((resolve, reject) => {
      promises = Array.isArray(promises) ? promises : Array.from(promises);
      if (!promises || promises.length === 0) {
        return reject(new AggregateError([]));
      }

      const errors = new Array(promises.length);
      let settled = 0;

      for (let i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i])
          .then(value => {
            resolve(value);
          })
          .catch(error => {
            errors[i] = error;
            settled++;
            if (settled === promises.length) {
              reject(new AggregateError(errors));
            }
          });
      }
    });
  };
}

这段代码会检查浏览器是否支持Promise.any(),如果不支持,就创建一个自己的实现。

最佳实践:用好Promise.any()的几个小技巧!

  • 错误处理: 务必处理AggregateError,了解所有Promise失败的原因。
  • 性能考虑: 如果Promise数量很多,可能会影响性能。
  • 清晰的Promise: 确保每个Promise都有清晰的成功和失败逻辑。
  • 超时处理: 结合Promise.race(),为每个Promise设置超时时间。

总结:Promise.any(),你的新朋友!

Promise.any()是一个非常有用的Promise方法,它可以让我们更轻松地处理多个Promise,并且只需要其中一个成功即可。它特别适用于需要从多个来源获取数据,或者需要处理多个备选方案的场景。

希望今天的讲座能帮助大家更好地理解和使用Promise.any()。下次再见! 记住,Promise的世界,永远充满惊喜!

发表回复

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