JS `Promise.allSettled` (ES2021):等待所有 Promise 完成并获取结果

好的,各位观众老爷,今天咱们聊聊Promise.allSettled这个ES2021的新玩意儿。这玩意儿可真是Promise家族里的一股清流,专门收拾烂摊子,保证你的程序不会因为某个Promise的错误而崩盘。

开场白:Promise,爱恨交织

话说Promise这东西,大家都用过吧?用得好,异步操作井井有条;用不好,回调地狱让你怀疑人生。Promise解决了回调地狱的问题,但它也带来了一个新的问题:错误处理。

特别是当你需要同时处理多个Promise的时候,如果其中一个Promise失败了,整个Promise链条就会被中断,后面的操作就都没戏了。这就像多米诺骨牌,倒了一个全倒了。

Promise.all就是个典型的例子。它会等待所有Promise都resolve,只要有一个reject,整个Promise.all就会立即reject。这在某些场景下是合理的,比如你需要所有数据都成功才能进行下一步操作。但有些时候,你并不需要这么严格,即使某些Promise失败了,你仍然希望知道其他Promise的结果。

这时候,Promise.allSettled就闪亮登场了!

Promise.allSettled:佛系Promise

Promise.allSettled就像一个佛系Promise,它会等待所有Promise都完成,不管它们是resolve还是reject。它不会因为其中一个Promise的失败而提前结束。它会返回一个包含所有Promise结果的数组,每个结果对象都包含一个status属性和一个valuereason属性。

  • status: 字符串,表示Promise的状态,可以是'fulfilled''rejected'
  • value: 如果Promise是resolve的,则value属性包含resolve的值。
  • reason: 如果Promise是reject的,则reason属性包含reject的原因。

举个例子:

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('出错了!');
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve(3), 100);
});

Promise.allSettled([promise1, promise2, promise3])
  .then((results) => {
    console.log(results);
  });

// 输出:
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: '出错了!' },
//   { status: 'fulfilled', value: 3 }
// ]

可以看到,即使promise2 reject 了,Promise.allSettled 仍然等待了所有Promise完成,并返回了一个包含所有结果的数组。我们可以通过检查status属性来判断每个Promise是成功还是失败,并根据需要处理valuereason

Promise.all vs Promise.allSettled:一场友谊赛

为了更好地理解Promise.allSettled的优势,我们把它和Promise.all放在一起比较一下。

特性 Promise.all Promise.allSettled
成功条件 所有Promise都resolve 所有Promise都完成 (resolve或reject)
失败条件 只要有一个Promise reject,立即reject整个Promise 等待所有Promise完成
返回值 resolve的值是一个包含所有Promise resolve值的数组 resolve的值是一个包含所有结果对象的数组,每个对象包含status, valuereason
适用场景 需要所有Promise都成功才能进行下一步操作的场景 即使某些Promise失败,仍然需要知道所有Promise结果的场景
容错性

从表格中可以看出,Promise.all更适合于需要所有Promise都成功的场景,而Promise.allSettled更适合于需要容错的场景。

实战演练:数据聚合,永不放弃

假设我们需要从多个API获取数据,然后将这些数据聚合在一起。但是,有些API可能会失败,我们不希望因为某个API的失败而导致整个数据聚合失败。这时候,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) {
    console.error(`Failed to fetch ${url}: ${error}`);
    throw error; // 重要的是重新抛出错误,让 allSettled 捕获
  }
}

const apiUrls = [
  'https://jsonplaceholder.typicode.com/todos/1',
  'https://jsonplaceholder.typicode.com/posts/1',
  'https://jsonplaceholder.typicode.com/users/1',
  'https://example.com/broken-api' // 模拟一个错误的API
];

async function aggregateData() {
  const promises = apiUrls.map(url => fetchData(url));

  const results = await Promise.allSettled(promises);

  const successfulData = [];
  const failedRequests = [];

  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      successfulData.push({ url: apiUrls[index], data: result.value });
    } else {
      failedRequests.push({ url: apiUrls[index], reason: result.reason });
    }
  });

  console.log('Successful Data:', successfulData);
  console.log('Failed Requests:', failedRequests);

  return { successfulData, failedRequests };
}

aggregateData();

在这个例子中,我们定义了一个fetchData函数,用于从指定的URL获取数据。如果API返回错误,fetchData函数会抛出一个错误。

然后,我们使用Promise.allSettled来同时获取多个API的数据。即使某个API失败了,Promise.allSettled仍然会等待所有API完成,并返回一个包含所有结果的数组。

最后,我们遍历结果数组,将成功获取的数据和失败的请求分别存储到successfulDatafailedRequests数组中。

这样,即使某些API失败了,我们仍然可以获取到其他API的数据,并知道哪些API失败了,以及失败的原因。

错误处理:优雅地处理失败

Promise.allSettled本身并不能处理错误,它只是收集所有Promise的结果,包括错误。我们需要自己处理这些错误。

在上面的例子中,我们通过检查result.status来判断Promise是成功还是失败,然后根据需要处理result.valueresult.reason

这种方式非常灵活,我们可以根据不同的错误类型采取不同的处理方式。例如,我们可以记录错误日志,显示错误提示,或者重试失败的请求。

results.forEach((result, index) => {
  if (result.status === 'fulfilled') {
    successfulData.push({ url: apiUrls[index], data: result.value });
  } else {
    console.error(`Request to ${apiUrls[index]} failed:`, result.reason); // 记录错误日志
    // 可以在这里显示错误提示
    // 可以在这里重试失败的请求
    failedRequests.push({ url: apiUrls[index], reason: result.reason });
  }
});

兼容性:放心使用

Promise.allSettled是ES2021的新特性,这意味着它在一些旧版本的浏览器中可能不支持。但是,我们可以使用polyfill来解决这个问题。

polyfill是一种代码,它可以模拟旧版本浏览器不支持的新特性。我们可以使用core-js这个polyfill库来polyfill Promise.allSettled

首先,安装core-js:

npm install core-js

然后,在你的代码中引入core-js:

import 'core-js/actual/promise/all-settled';

// 现在你可以安全地使用 Promise.allSettled 了

这样,即使你的用户使用的是旧版本的浏览器,你的代码也可以正常运行。

高级用法:自定义状态处理

有时候,我们可能需要根据Promise的结果执行一些自定义的操作。例如,我们可能需要在Promise成功时更新UI,或者在Promise失败时显示错误信息。

我们可以使用Promise.allSettled的返回值来执行这些自定义的操作。

Promise.allSettled(promises)
  .then((results) => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        // 更新UI
        updateUI(index, result.value);
      } else {
        // 显示错误信息
        showErrorMessage(index, result.reason);
      }
    });
  });

function updateUI(index, data) {
  // 根据数据更新UI
  console.log(`Updating UI for promise ${index} with data:`, data);
}

function showErrorMessage(index, reason) {
  // 显示错误信息
  console.error(`Promise ${index} failed:`, reason);
}

在这个例子中,我们定义了updateUIshowErrorMessage两个函数,分别用于更新UI和显示错误信息。

然后,我们遍历Promise.allSettled的返回值,根据Promise的结果调用相应的函数。

这样,我们就可以根据Promise的结果执行一些自定义的操作,使我们的程序更加灵活和可控。

总结:Promise.allSettled,你的异步好帮手

Promise.allSettled是一个非常有用的Promise API,它可以帮助我们更好地处理异步操作,特别是当我们需要同时处理多个Promise,并且不希望因为某个Promise的失败而导致整个程序崩溃的时候。

它具有以下优点:

  • 容错性强: 即使某些Promise失败,它仍然会等待所有Promise完成。
  • 灵活性高: 我们可以根据Promise的结果执行自定义的操作。
  • 易于使用: 它的API非常简单易懂。

所以,下次当你需要处理多个Promise的时候,不妨试试Promise.allSettled,它可能会给你带来惊喜。

最后的彩蛋:Promise.anyPromise.race

既然都说到Promise家族了,顺便提一下另外两个常用的Promise API:Promise.anyPromise.race

  • Promise.any (ES2021): 只要有一个Promise resolve,Promise.any就会resolve,并返回第一个resolve的值。如果所有Promise都reject,Promise.any会reject,并返回一个AggregateError,其中包含所有reject的原因。 它像一个乐观主义者,只要有一个成功,就认为一切都好。

  • Promise.race: Promise.race会等待第一个Promise完成 (resolve或reject),并返回该Promise的结果。 它可以用来设置Promise的超时时间,或者在多个Promise中选择最快的那个。 就像赛跑一样,谁先到终点,就返回谁的结果。

希望今天的讲解对大家有所帮助! 祝大家编程愉快!

发表回复

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