JS Promise.all()

Promise.all() 静态方法,输入一系列可迭代期约,输出一个期约。

  1. 如果输入的系列期约都实现(fulfill),即使输入的是空的迭代,输出的期约状态是 fulfill,返回一个实现值数组。
  2. 如果有任何一个输入期约是拒绝状态,返回的也是拒绝状态,附带第一个拒绝期约的理由。

Demo(示范):

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

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

语法

Promise.all(iterable)

参数

  • iterable 可迭代期约

返回值

返回一个期约:

  • 已完成的,传递的可迭代期约为空。
  • 异步完成,当所有可迭代期约完成时。返回的完成值,是所有可迭代完成值的次序排列。如果传入的可迭代期约非空但包含非 pending 期约,返回期约仍是异步完成(不是同步)。
  • 异步拒绝,传入期约有拒绝状态。拒绝原因来自传入的第一个拒绝状态的期约。

描述

Promise.all() 是几个期约并发方法之一。它能够聚合多个期约的运行结果。在执行多个异步任务时,我们希望每个任务都能成功执行,此时就可以用 Promise.all() 。如果输入的期约有 reject 状态的, Promise.all() 立刻返回 reject。与之对应的有 Promise.allSettled() ,它是只要输入一个 fulfill 状态的期约,就能返回 fulfill 状态期约。

示例

等待全部完成或出现 reject

const p1 = Promise.resolve(3);
const p2 = 1337;
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 100);
});

Promise.all([p1, p2, p3]).then((values) => {
  console.log(values);
});

// Promise { <state>: "pending" }
//   <state>: "fulfilled"
// Array(3) [ 3, 1337, "foo" ]

可迭代部分包含非期约部分,会被忽略,但仍在返回值数组中保留。

const p = Promise.all([1, 2, 3]);
const p2 = Promise.all([1, 2, 3, Promise.resolve(444)]);
const p3 = Promise.all([1, 2, 3, Promise.reject(555)]);

setTimeout(() => {
  console.log(p);
  console.log(p2);
  console.log(p3);
});

// Promise { <state>: "fulfilled", <value>: [1, 2, 3]}
// Promise { <state>: "rejected", <reason>: 555 }
// Uncaught (in promise) 555

Promise.all 的异步与同步

异步 resolve:

const resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
const p = Promise.all(resolvedPromisesArray);
console.log(p);

// Promise { <state>: "pending" }

setTimeout(() => {
  console.log("the queue is now empty");
  console.log(p)
});

// the queue is now empty
// Promise { <state>: "fulfilled", <value>: [33, 44]}

异步 reject:

const mixedPromisesArray = [Promise.resolve(33), Promise.reject(44)];
const p = Promise.all(mixedPromisesArray);
console.log(p);
// Promise { <state>: "pending" }
//   <state>: "rejected"
//   <reason>: 44

setTimeout(() => {
  console.log("the queue is now empty");
  console.log(p);
)};
// the queue is now empty
// Promise { <state>: "rejected", <reason>: 44 }

同步:

const p = Promise.all([]);
const p2 = Promise.all([1337, "hi"]); // 非期约值会被忽略,但计算结果是异步的
console.log(p);
console.log(p2);
// Promise { <state>: "fulfilled", <value>: [] }
// Promise { <state>: "pending" }
//   <state>: "fulfilled"
//   <value>: Array [ 1337, "hi" ]

setTimeout(() => {
  console.log("the queue is now empty");
  console.log(p2);
});
// the queue is now empty
// Promise { <state>: "fulfilled", <value>: [1337, "hi"] }

为什么加个延时,Promise 就会从 pending 变为 fulfilled?

用 Promise.all() 搭配 async 函数

async 函数中,“过度等待”代码很常见。

错误代码:

async function getPrice() {
  const choice = await promptForDishChoice();
  const prices = await fetchPrices();
  return prices[choice];

getPrice() 异步函数中的两个函数并不互相依赖。但是在以上代码中,await 的存在导致:如果 promptForDishChoice() 没有执行完成,下面的代码就无法执行。可以用 Promise.all() 并列执行这两个函数。

正确写法:

async function getPrice() {
  const [choice, prices] = await Promise.all([
    promptForDishChoice(),
    fetchPrices(),
  ]);
  return prices[choice];

Promise.all() 用在这里是几个并发方法中最合适的,因为错误处理是直观的——如果输入的期约有 reject 状态的,返回的结果就不可用。

Promise.all() 接受一系列可迭代期约,因此如果使用它来平行执行多个异步函数,需要调用 async 函数,并且使用可返回的期约。直接将函数传递给 Promise.all 行不通,因为它们不是期约。

错误代码:

async function getPrice() {
  const [choice, prices] = await Promise.all([
    promptForDishChoice,
    fetchPrices,
  ]);
}

Promise.all() 快速失败行为

如果传入的期约有一个状态是 reject,就返回 reject 状态。

四个 resolve,第五个 reject:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 1000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("three"), 3000);
});
const p4 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("four"), 4000);
});
const p5 = new Promise((resolve, reject) => {
  reject(new Error("reject"));
});

Promise.all([p1, p2, p3, p4, p5])
  .then((values) => {
    console.log(values);
  })
  .catch((error) => {
    console.error(error.message);
  });

最终只输出了 reject 状态。

可以通过修改 reject 细节,来增加输出内容:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p1_delayed_resolution'), 1000)
})
const p2 = new Promise((resolve, reject) => {
  reject(new Error('p2_immediate_rejection'))
})

Promise.all([p1.catch((error) => error), p2.catch((error) => error)]).then(
  (values) => {
    console.log(values[0])
    console.log(values[1])
  }
)

/**
 * Promise { <state>: "pending" }
 * p1_delayed_resolution
 * Error: p2_immediate_rejection
 */

ES 标准

https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.all

This function returns a new promise which is fulfilled with an array of fulfillment values for the passed promises, or rejects with the reason of the first passed promise that rejects. It resolves all elements of the passed iterable to promises as it runs this algorithm.

实现 Promise.all()

Promise.all = function (proms) {
  return new Promise((resolve, reject) => {
    try {
      const results = []
      let count = 0
      let fulfilledCount = 0
      for (const p of proms) {
        let i = count
        count++

        Promise.resolve(p).then((data) => {
          fulfilledCount++
          results[i] = data
          console.log(fulfilledCount)
          if (fulfilledCount === count) {
            console.log('全部完成')
            resolve(results)
          }
        }, reject)
      }
      console.log(count, fulfilledCount)
      if (count === 0) {
        resolve(results)
      }
    } catch (error) {
      reject(error)
      console.error(error)
    }
  })
}

// 测试
Promise.all([Promise.reject(1), Promise.resolve(2), Promise.resolve(3), 4])
  .then((data) => {
    console.log('成功', data)
  },
  (reason) => {
    console.log('失败', reason)
  }
  )
// 4 0
// 1
// 2
// 3
// 失败 1

参考资料

  1. Promise.all() - JavaScript | MDN
  2. 1. 实现一个Promise.all · Issue #1 · Sunny-117/js-challenges
欢迎通过「邮件」或者点击「这里」告诉我你的想法
Welcome to tell me your thoughts via "email" or click "here"