面试官:讲一下Promise.all 和 Promise.allSettled的区别

哈喽,大家好,今天的文章由团队中的Uncle13提供。


Promise也算是前端面试中的高频面试题了,比如代码执行题、手撕代码题都少不了Promise。今天和大家分享一下Promise.all和Promise.allSettled的区别。


以下是正文:


Promise.allPromise.allSettled 都是 JavaScript 中处理 Promise 对象的两个很有用的方法,但它们之间存在一些重要的区别。

Promise.all

Promise.all 方法接收一个 Promise 对象的数组作为参数,并返回一个新的 Promise 对象。这个新的 Promise 对象会在数组中的所有 Promise 对象都成功解决(resolve)后解决,并且它的解决值是一个包含所有解决值的数组。然而,如果数组中的任何一个 Promise 对象被拒绝(reject),那么 Promise.all 会立即以该拒绝原因被拒绝。

Promise.all 简单的例子:

let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
  // expected output: Array [3, 42, "foo"]
});

Promise.allSettled

Promise.allSettled 的行为类似于 Promise.all,但它不会因为数组中的某个 Promise 对象被拒绝而立即被拒绝。相反,它会等待数组中的所有 Promise 对象都解决或被拒绝,然后返回一个包含每个 Promise 对象状态的数组。每个状态对象都有一个 status 属性(值为 'fulfilled''rejected'),以及一个 valuereason 属性(取决于 Promise 是解决还是被拒绝)。

Promise.allSettled简单的例子:

let promise1 = Promise.resolve(3);
let promise2 = new Promise((resolve, reject) => reject('失败!'));
let promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100'foo');
});

Promise.allSettled([promise1, promise2, promise3]).
  then((results) => results.forEach((result) => console.log(result.status));
// expected output:
// "fulfilled"
// "rejected"
// "fulfilled"

复杂的常见场景

1:异步数据加载

假设我们有一个网页,需要从两个不同的 API 加载数据。我们希望在所有数据都加载完成后才更新页面。

使用 Promise.all
function fetchDataFromApi1({
  return new Promise((resolve, reject) => {
    // 模拟异步请求
    setTimeout(() => {
      resolve('数据来自 API 1');
    }, 1000);
  });
}

function fetchDataFromApi2({
  return new Promise((resolve, reject) => {
    // 模拟另一个异步请求,可能会失败
    setTimeout(() => {
      reject('API 2 请求失败');
    }, 500);
  });
}

Promise.all([fetchDataFromApi1(), fetchDataFromApi2()])
  .then(([data1, data2]) => {
    // 假设这是更新页面的逻辑
    console.log('页面更新:', data1, data2);
  })
  .catch((error) => {
    // 如果任何一个 Promise 被拒绝,这里会捕获到错误
    console.error('数据加载失败:', error);
  });

// 输出: 数据加载失败: API 2 请求失败

由于 fetchDataFromApi2 失败了,Promise.all 会立即以 API 2 请求失败 为由被拒绝,因此不会执行 .then 中的页面更新逻辑。

使用 Promise.allSettled
Promise.allSettled([fetchDataFromApi1(), fetchDataFromApi2()])
  .then((results) => {
    // 处理每个 Promise 的结果,无论成功或失败
    const [result1, result2] = results;
    if (result1.status === 'fulfilled') {
      console.log('API 1 数据:', result1.value);
    } else {
      console.error('API 1 失败:', result1.reason);
    }
    if (result2.status === 'fulfilled') {
      console.log('API 2 数据:', result2.value);
    } else {
      console.error('API 2 失败:', result2.reason);
    }
    // 尽管 API 2 失败,但我们仍然可以处理 API 1 的数据,或者执行其他逻辑
  });

// 输出:
// API 1 数据: 数据来自 API 1
// API 2 失败: API 2 请求失败

使用 Promise.allSettled,即使 fetchDataFromApi2 失败了,我们仍然可以处理 fetchDataFromApi1 成功返回的数据,并且不会中断整个流程。

2:表单验证

假设我们有一个表单,每个字段都需要进行异步验证(例如,检查用户名是否已被注册)。我们想要在所有验证完成后才提交表单。

使用 Promise.all
function validateUsername(username{
  return new Promise((resolve, reject) => {
    // 模拟异步验证
    setTimeout(() => {
      if (username === 'existing') {
        reject('用户名已被注册');
      } else {
        resolve('用户名可用');
      }
    }, 500);
  });
}

function validateEmail(email{
  // 类似地,这里可以模拟另一个异步验证过程
}

const username = 'existing';
const email = 'user@example.com';

Promise.all([validateUsername(username), validateEmail(email)])
  .then(([usernameValidation, emailValidation]) => {
    // 如果所有验证都通过,则提交表单
    console.log('表单可以提交:', usernameValidation, emailValidation);
  })
  .catch((error) => {
    // 如果任何一个验证失败,则显示错误信息
    console.error('验证失败:', error);
  });

// 输出: 验证失败: 用户名已被注册

如果用户名验证失败,Promise.all 会立即被拒绝,不会执行表单提交逻辑。

使用 Promise.allSettled
Promise.allSettled([validateUsername(username), validateEmail(email)])
  .then((results) => {
    // 处理每个验证的结果,无论成功或失败
    const [usernameResult, emailResult] = results;
    if (usernameResult.status === 'fulfilled') {
      console.log('用户名验证成功:', usernameResult.value)
    } else {
      console.error('用户名验证失败:', usernameResult.reason);
    }
    if (emailResult.status === 'fulfilled') {
      console.log('邮箱验证成功:', emailResult.value);
    } else {
      console.error('邮箱验证失败:', emailResult.reason);
    }
    
    // 我们可以根据验证结果决定下一步行动,比如显示错误提示或者仅提交部分通过验证的字段
    // 即使用户名验证失败,我们仍然可以处理邮箱验证的结果
  });

// 输出:
// 用户名验证失败: 用户名已被注册
// 邮箱验证成功: 邮箱可用

使用 Promise.allSettled 允许我们即使用户名验证失败,也仍然可以处理邮箱验证的结果。这样,我们可以为用户提供更详细的反馈,比如同时显示用户名和邮箱的验证状态,而不是在用户名验证失败时立即中断整个流程。

总结

Promise.allPromise.allSettled 都是用于处理 Promise 对象数组的方法,但它们的行为和用途有所不同。

  • Promise.all 等待数组中的所有 Promise 对象都成功解决(resolve)后,才返回一个新的、已解决的 Promise,其解决值是一个包含所有解决值的数组。如果数组中的任何一个 Promise 对象被拒绝(reject),则 Promise.all 会立即以该拒绝原因被拒绝。这种方法适用于需要所有 Promise 都成功的情况。

  • Promise.allSettled 则会等待数组中的所有 Promise 对象无论解决还是被拒绝都完成后,才返回一个新的、已解决的 Promise。其解决值是一个数组,数组中的每个元素都是一个对象,描述了对应 Promise 的最终状态(解决或被拒绝),以及相应的值或原因。即使某些 Promise 被拒绝,Promise.allSettled 也不会提前结束,而是会收集所有结果。这种方法适用于需要了解每个 Promise 的最终状态,无论其是否成功的情况。

简而言之,Promise.all 关注于所有 Promise 是否都成功,而 Promise.allSettled 则关注于所有 Promise 是否都已完成(无论成功或失败),并提供了每个 Promise 的最终结果。


最后

还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。

老规矩,也给我们团队的辅导服务打个广告。