深入理解 JavaScript 中 Promise.allSettled() 和 Promise.any() (ES2021) 的设计意图和应用场景。

好的,各位听众,咱们今天来聊聊 JavaScript ES2021 里两个挺有意思的家伙:Promise.allSettled()Promise.any()。 这俩哥们儿虽然不像 Promise.all()Promise.race() 那么频繁露脸,但在某些特定场景下,绝对能让你眼前一亮,大喊一声“真香!”。

开场白:Promise 的世界

在深入了解 Promise.allSettled()Promise.any() 之前,咱们先简单回顾一下 Promise 的基本概念。Promise 简单来说,就是一个代表异步操作最终完成(或失败)的对象。它可以处于三种状态:

  • pending (进行中): 初始状态,既没有成功,也没有失败。
  • fulfilled (已成功): 操作成功完成。
  • rejected (已失败): 操作失败。

Promise 解决了回调地狱的问题,让异步代码更易于管理和阅读。我们经常用到的 Promise.all()Promise.race() 就像 Promise 世界里的明星,但今天我们要介绍的 Promise.allSettled()Promise.any() 就像是默默耕耘的幕后英雄,虽然不常被提及,但关键时刻能发挥重要作用。

第一幕:Promise.allSettled() – 不抛弃,不放弃

设计意图

Promise.allSettled() 的设计意图非常明确:无论传入的 Promise 是成功还是失败,都要等到所有 Promise 都完成(settled)后,才返回一个包含所有 Promise 结果的数组。 换句话说,它不会像 Promise.all() 那样,只要有一个 Promise 失败就直接抛出错误,而是会收集所有 Promise 的结果,无论成功与否。

这种“不抛弃,不放弃”的精神,让 Promise.allSettled() 在处理需要收集所有结果的场景下非常有用,即使某些 Promise 失败了,我们仍然可以获取其他 Promise 的成功结果。

应用场景

想象一下这样一个场景:你需要同时请求多个 API 接口,然后根据所有接口的返回结果来更新 UI。但是,你并不希望因为其中一个接口请求失败就导致整个流程中断。这时,Promise.allSettled() 就派上用场了。

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    return Promise.reject(error); // 明确返回一个 rejected Promise
  }
}

const urls = [
  'https://jsonplaceholder.typicode.com/todos/1', // 肯定成功
  'https://jsonplaceholder.typicode.com/todos/12345', // 肯定失败 (404)
  'https://jsonplaceholder.typicode.com/posts/1', // 肯定成功
];

async function processData() {
  const results = await Promise.allSettled(urls.map(url => fetchData(url)));

  console.log(results); // 打印所有结果,包括成功和失败

  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log('成功:', result.value);
    } else {
      console.error('失败:', result.reason);
    }
  });
}

processData();

在这个例子中,即使其中一个 URL 返回 404 错误,Promise.allSettled() 仍然会等待所有 Promise 完成,然后返回一个包含所有结果的数组。我们可以遍历这个数组,分别处理成功和失败的结果。

结果结构

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

  • status: 字符串,表示 Promise 的状态,可以是 'fulfilled''rejected'
  • value: 如果 status'fulfilled',则 value 属性包含 Promise 的成功结果。
  • reason: 如果 status'rejected',则 reason 属性包含 Promise 的失败原因。

用表格总结一下:

属性 类型 描述
status string Promise 的状态,'fulfilled''rejected'
value any Promise 成功的结果 (仅当 status'fulfilled')
reason any Promise 失败的原因 (仅当 status'rejected')

与 Promise.all() 的对比

Promise.all()Promise.allSettled() 的主要区别在于错误处理:

特性 Promise.all() Promise.allSettled()
错误处理 只要有一个 Promise 失败,就立即抛出错误,并中断执行 等待所有 Promise 完成,收集所有结果,包括成功和失败
返回值 成功时返回一个包含所有 Promise 结果的数组,失败时抛出错误 返回一个包含所有 Promise 结果的数组,每个结果包含 statusvaluereason
适用场景 所有 Promise 都必须成功才能继续执行的场景 允许部分 Promise 失败,但仍需要收集所有结果的场景

第二幕:Promise.any() – 只要有一个成功,我就算你赢

设计意图

Promise.allSettled() 的“不抛弃,不放弃”相反,Promise.any() 的设计意图是:只要传入的 Promise 中有一个成功,就立即返回该成功 Promise 的结果。 只有当所有 Promise 都失败时,才会抛出一个 AggregateError 错误,其中包含所有 Promise 的失败原因。

Promise.any() 的这种“一将功成万骨枯”的精神,让它在处理需要多个备选方案,只要有一个方案成功就可以的场景下非常有用。

应用场景

假设你需要从多个 CDN 加载同一个资源,但你不知道哪个 CDN 的速度最快。你可以使用 Promise.any() 来同时请求所有 CDN,只要有一个 CDN 成功返回资源,就立即使用该资源,而无需等待其他 CDN。

async function fetchFromCDN(cdnUrl) {
  try {
    const response = await fetch(cdnUrl);
    if (!response.ok) {
      throw new Error(`CDN error! status: ${response.status}`);
    }
    return await response.text(); // 假设加载的是文本资源
  } catch (error) {
    return Promise.reject(error); // 明确返回一个 rejected Promise
  }
}

const cdnUrls = [
  'https://cdn1.example.com/resource.txt', // 假设这个 CDN 速度很快
  'https://cdn2.example.com/resource.txt', // 假设这个 CDN 速度很慢
  'https://cdn3.example.com/resource.txt', // 假设这个 CDN 可能挂了
];

async function loadResource() {
  try {
    const resource = await Promise.any(cdnUrls.map(url => fetchFromCDN(url)));
    console.log('成功加载资源:', resource);
  } catch (error) {
    console.error('所有 CDN 都失败了:', error);
    if (error instanceof AggregateError) {
      console.log('失败原因:', error.errors); // 打印所有失败原因
    }
  }
}

loadResource();

在这个例子中,Promise.any() 会同时请求所有 CDN。只要其中一个 CDN 成功返回资源,Promise.any() 就会立即返回该资源,而无需等待其他 CDN。如果所有 CDN 都失败了,Promise.any() 会抛出一个 AggregateError 错误,其中包含所有 CDN 的失败原因。

AggregateError

Promise.any() 中所有 Promise 都失败时,会抛出一个 AggregateError 错误。AggregateError 是一个特殊的 Error 对象,它包含一个 errors 属性,该属性是一个数组,包含所有 Promise 的失败原因。

try {
  await Promise.any([
    Promise.reject('Error 1'),
    Promise.reject('Error 2'),
    Promise.reject('Error 3'),
  ]);
} catch (error) {
  console.error(error); // AggregateError: All promises were rejected
  console.log(error.errors); // ['Error 1', 'Error 2', 'Error 3']
}

与 Promise.race() 的对比

Promise.any()Promise.race() 都是用于处理多个 Promise 的,但它们的行为有所不同:

特性 Promise.race() Promise.any()
成功处理 只要有一个 Promise 完成(成功或失败),就立即返回该 Promise 的结果 只要有一个 Promise 成功,就立即返回该成功 Promise 的结果
失败处理 只要有一个 Promise 失败,就立即抛出错误 只有当所有 Promise 都失败时,才抛出一个 AggregateError 错误
适用场景 只需要最先完成的 Promise 的结果的场景 需要多个备选方案,只要有一个方案成功就可以的场景

第三幕:实战演练 – 图片上传优化

让我们来看一个更实际的例子:图片上传优化。假设你需要将一张图片上传到多个服务器,以提高上传的成功率和速度。你可以使用 Promise.any() 来实现这个功能。

async function uploadImage(serverUrl, imageFile) {
  try {
    const formData = new FormData();
    formData.append('image', imageFile);

    const response = await fetch(serverUrl, {
      method: 'POST',
      body: formData,
    });

    if (!response.ok) {
      throw new Error(`Upload failed! status: ${response.status}`);
    }

    const data = await response.json();
    return data.imageUrl; // 假设服务器返回图片 URL
  } catch (error) {
    return Promise.reject(error); // 明确返回一个 rejected Promise
  }
}

async function uploadImageToMultipleServers(imageFile, serverUrls) {
  try {
    const imageUrl = await Promise.any(
      serverUrls.map(url => uploadImage(url, imageFile))
    );
    console.log('图片上传成功,URL:', imageUrl);
    return imageUrl;
  } catch (error) {
    console.error('所有服务器上传都失败了:', error);
    // 处理所有服务器都失败的情况
    return null;
  }
}

// 示例
const imageFile = new File(['dummy data'], 'image.jpg', { type: 'image/jpeg' });
const serverUrls = [
  'https://server1.example.com/upload',
  'https://server2.example.com/upload',
  'https://server3.example.com/upload',
];

uploadImageToMultipleServers(imageFile, serverUrls);

在这个例子中,uploadImageToMultipleServers() 函数会将图片上传到多个服务器。只要其中一个服务器上传成功,Promise.any() 就会立即返回该服务器返回的图片 URL。如果所有服务器都上传失败,Promise.any() 会抛出一个 AggregateError 错误,我们可以根据需要进行处理。

总结:选择合适的工具

Promise.allSettled()Promise.any() 都是非常有用的工具,可以帮助我们更好地处理异步操作。选择哪个工具取决于你的具体需求:

  • 如果你需要收集所有 Promise 的结果,无论成功与否,都应该使用 Promise.allSettled()
  • 如果你只需要一个 Promise 成功就可以,并且不关心其他 Promise 的结果,就应该使用 Promise.any()

希望今天的讲座能帮助你更好地理解 Promise.allSettled()Promise.any() 的设计意图和应用场景。记住,选择合适的工具,才能事半功倍!感谢大家的聆听!

发表回复

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