Promise常考编程题

26 阅读2分钟

Promise.all

level1: 经典永不过时(❤)

Promise.all =
  Promise.all ||
  function (promises) {
    // 返回一个新的 Promise
    return new Promise((resolve, reject) => {
      // 处理非数组输入
      if (!Array.isArray(promises)) {
        return reject(new TypeError("参数必须是一个数组"));
      }

      // 处理空数组的情况
      if (promises.length === 0) {
        return resolve([]);
      }

      const results = []; // 存储所有 Promise 的结果
      let completedCount = 0; // 记录已完成的 Promise 数量
      const total = promises.length;

      // 遍历所有 promise
      promises.forEach((promise, index) => {
        // 使用 Promise.resolve 包装,确保处理非 Promise 值
        Promise.resolve(promise)
          .then((value) => {
            // 按照原始顺序保存结果
            results[index] = value;
            completedCount++;

            // 当所有 Promise 都完成时,resolve 结果数组
            if (completedCount === total) {
              resolve(results);
            }
          })
          .catch((error) => {
            // 任何一个 Promise reject,立即 reject
            reject(error);
          });
      });
    });
  };

level2: var + for 循环的经典闭包陷阱(❤)

Promise.all =
  Promise.all ||
  function (promises) {
    return new Promise(function (resolve, reject) {
      if (!Array.isArray(promises)) {
        return reject(new TypeError("参数必须是一个数组"));
      }

      if (promises.length === 0) {
        return resolve([]);
      }

      var results = [];
      var completedCount = 0;
      var total = promises.length;

      // 提取处理函数
      function handlePromise(promise, index) {
        Promise.resolve(promise)
          .then(function (value) {
            results[index] = value;
            completedCount++;

            if (completedCount === total) {
              resolve(results);
            }
          })
          .catch(function (error) {
            reject(error);
          });
      }

      // 循环调用
      for (var i = 0; i < promises.length; i++) {
        handlePromise(promises[i], i);
      }
    });
  };

闭包 = 内部函数 + 外部变量的引用

如果不使用闭包,那么

  • var是函数作用域,变量提升

  • 循环结束后,i 的值是 promises.length

  • 所有 .then() 回调共享同一个 i 变量

  • 当异步回调执行时,i 已经是最终值,导致所有结果都写入错误的索引

如果不使用闭包会怎么样?

❌ 问题示例:不提取 handlePromise 函数

Promise.all = function (promises) {
    return new Promise(function (resolve, reject) {
        var results = [];
        var completedCount = 0;
        var total = promises.length;
        
        for (var i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i])
              .then(function (value) {
                  // 问题:i 已经变成了 promises.length!
                  results[i] = value;  // ❌ 错误的索引
                  completedCount++;
                  if (completedCount === total) {
                      resolve(results);
                  }
              });
        }
    });
};

结果:

Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
  .then(console.log);
// 期望:[1, 2, 3]
// 实际:[undefined, undefined, undefined, 3] (最后一个值覆盖了所有)

Promise.race

Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    for (const promise of promises) {
      Promise.resolve(promise).then(resolve, reject);
    }
  });
};

追问:

Promise.race 与 Promise.all 的区别

// 要求:说明两者的差异和使用场景
Promise.all([p1, p2, p3]);  // 全部成功才成功,一个失败就失败
Promise.race([p1, p2, p3]); // 第一个完成(成功/失败)就返回
  • 如果 Promise.race([]) 会怎样?(永远 pending)
  • 如果 Promise.all([]) 会怎样?(立即 resolve([]))

推荐不使用 .then() 的第二个参数(容易漏掉回调中的错误)


Promise.allSettled

Promise.allSettled 会等待所有 Promise 完成(无论成功或失败),然后返回一个包含所有结果的数组。

Promise.allSettled = function(promises) {
  return Promise.all(
    promises.map(p =>
      Promise.resolve(p).then(
        value => ({ status: 'fulfilled', value }),
        reason => ({ status: 'rejected', reason })
      )
    )
  );
};

总结: 把每个 Promise 不管成功失败,都包装成成功的,装进对象里返回,这样就不会中途退出了

// 原本可能失败的 Promise
Promise.reject('错误')

// 包装后变成成功的,只是结果里标记了状态
Promise.resolve({ status: 'rejected', reason: '错误' })

与 Promise.all 的区别

  • Promise.all: 任何一个 reject 就立即失败
  • Promise.allSettled: 永不 reject,等所有完成后返回结果数组

Promise.any

只要有一个成功就返回,全失败才 reject

Promise.any = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    if (promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'));
    }
    
    const errors = [];
    let rejectedCount = 0;
    const total = promises.length;
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(resolve)
        .catch(error => {
          errors[index] = error;
          rejectedCount++;
          if (rejectedCount === total) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        });
    });
  });
};