手写 Promise.all

题目:复刻 Promise.all:接收一个可迭代对象,所有 promise 都 fulfilled 时按顺序返回结果数组;任何一个 reject 则立即 reject。

1. 正常使用示例

下面为 Promise.all 的功能展示。正常情况下我们应该输出数组对象 [1, 2, 3]。

let promise1 = new Promise(function(resolve) {
  resolve(1);
});
let promise2 = new Promise(function(resolve) {
  resolve(2);
});
let promise3 = new Promise(function(resolve) {
  resolve(3);
});

let promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then(function(res) {
  console.log(res);
});

2. 最简实现(有缺陷)

Promise.all = function(promises) {
  let results = [];
  return new Promise(function(resolve) {
    promises.forEach(function(val) {
      // 按顺序执行每一个Promise操作
      val.then(function(res) {
        results.push(res);
      });
    });
    resolve(results);
  });
}

上面是最简化的版本,但是也有两个问题:一、Promise.all 传递的参数可能不是 Promise 类型,可能不存在 then 方法;二、如果中间发生错误,应该直接返回错误,不执行后面操作。

3. 改造版本(最终实现)

Promise.prototype.all = function (promises) {
  let results = [];
  let promiseCount = 0;
  const promisesLength = promises.length;
  return new Promise(function (resolve, reject) {
    for (let i = 0; i < promisesLength; i++) {
      Promise.resolve(promises[i]).then(
        function (res) {
          promiseCount++;
          results[i] = res; // 用闭包中的 i 保留位置,保证顺序
          if (promiseCount === promisesLength) {
            return resolve(results);
          }
        },
        function (err) {
          return reject(err);
        }
      );
    }
  });
};

补充:results[i] = res 必须保留原始下标 i,否则多个异步任务完成后会按完成顺序写入,导致最终数组顺序错乱。原笔记里直接写 results[i] 漏掉了闭包外层的 let i,需要换成 const i 或用 forEach。

来源整理自:vue3js.cn 面试官系列