手写Promise-静态方法all和allSettled

本文将深入探讨 Promise 的两个核心静态方法:Promise.allPromise.allSettled。相信你已经熟悉了它们的基本用法,但你是否曾好奇过,在这些便捷的 API 背后,究竟隐藏着怎样的实现机制?

当我们需要并行处理多个异步任务时,这两个方法成为了我们最得力的助手。Promise.all 秉承"全赢或全输"的果断哲学,而 Promise.allSettled 则展现了"有始有终"的包容智慧。理解它们的底层实现,不仅能让我们更加自信地运用它们,更是一次对 Promise 机制的深度探索之旅。

今天,就让我们亲手揭开它们的神秘面纱,从零开始,用代码实现这两个强大的工具。

📢 说明:本文涉及的某些函数或方法(如 definePropertyConfig)未在此处定义,其完整实现可参阅本专栏的往期文章。

搭建基本框架

js 复制代码
definePropertyConfig(Promise, "all", function (iterable) {
  /* 此处实现静态方法 all */
});

definePropertyConfig(Promise, "allSettled", function (iterable) {
  /* 此处实现静态方法 allSettled */
});

Promise.all实现

根据 MDN 的定义,Promise.all 具有以下特性:

从上述描述中,我们可以提取出关键信息:

  1. Promise.all 接收一个 Promise 可迭代对象作为参数
  2. 当所有 Promise 都成功完成时,返回一个包含所有兑现值的数组
  3. 只要有一个 Promise 失败,立即返回该失败原因

参数验证

让我们从参数验证开始实现 Promise.all。需要特别注意以下几点:

  • 调用者必须是 Promise 构造函数本身
  • 参数必须是一个可迭代对象
  • 最终返回一个新的 Promise 对象 基于以上考虑,代码实现如下:
js 复制代码
definePropertyConfig(Promise, "all", function (iterable) {
  // 验证调用者身份
  if (!(this === Promise)) {
    throw new TypeError("Promise.all called on non-constructor");
  }
  
  // 验证参数是否为可迭代对象
  if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
    throw new TypeError(
      `${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`
    );
  }
  
  return new Promise((resolve, reject) => {
    /* 此处实现核心逻辑 */
  });
});

初始化变量

在实现核心逻辑之前,我们需要准备以下变量:

  • results: 按顺序保存所有 Promise 的结果
  • completed: 已完成的 Promise 数量
  • count: Promise 的总数
  • isRejected: 标记是否已有 Promise 被拒绝
js 复制代码
definePropertyConfig(Promise, "all", function (iterable) {
  /* 参数验证代码已省略 */
  return new Promise((resolve, reject) => {
    var isRejected = false;    // 标记是否已被拒绝
    var results = [];        // 存储兑现值
    let count = 0;             // Promise 总数
    let completed = 0;         // 已完成的 Promise 数量
  });
});

处理边界情况

在继续实现核心逻辑之前,我们需要考虑一些边界情况:

js 复制代码
definePropertyConfig(Promise, "all", function (iterable) {
  /* 参数验证代码已省略 */
  return new Promise((resolve, reject) => {
    var isRejected = false; // 标记是否被reject
    var results = []; // 存储兑现值
    var count = 0; // 记录兑现数量
    var completed = 0; // 记录已经完成数量
    var promises = Array.from(iterable); // 将可迭代对象转换为数组
    var = promises.length;
    // 如果数组为空直接返回results
    if (count === 0) {
      Promise.resolve(results);
      return;
    }
    /* 核心逻辑将在下一步实现 */
  });
});

核心逻辑实现

js 复制代码
definePropertyConfig(Promise, "all", function (iterable) {
  /* 参数验证代码已省略 */
  return new Promise((resolve, reject) => {
    /* 变量声明和边界处理已省略 */
    // 遍历数组
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          if (isRejected) return; // 存在失败的则不再处理
          results[index] = value; // 将兑现值按照index对号入座存储起来,由于闭包的缘故,index不会存在错乱的问题
          completed += 1; // 记录已经兑现的数量
          // 比对已经兑现数量是否数组相等,相等表示已经完成,返回兑现值数组
          if (completed === count) {
            resolve(results);
          }
        })
        .catch((reason) => {
          if (isRejected) return; // 存在失败的则不再处理
          isRejected = true; // 如果有失败的 则记录为true
          reject(reason); // 返回失败原因
        });
    });
  });
});

完整实现代码

js 复制代码
definePropertyConfig(Promise, "all", function (iterable) {
  if (!(this === Promise)) {
    throw new TypeError("PromiseReject called on non-object");
  }
  if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
    throw new TypeError(
      "promises is not iterable (cannot read property Symbol(Symbol.iterator))"
    );
  }
  return new Promise((resolve, reject) => {
    var isRejected = false; // 标记是否被reject
    var results = []; // 存储兑现值
    var count = 0; // 记录兑现数量
    var completed = 0; // 记录已经完成数量
    var promises = Array.from(iterable);
    var = promises.length;
    if (count === 0) {
      resolve(results);
      return;
    }
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          if (isRejected) return;
          results[index] = value;
          completed += 1;
          if (completed === count) {
            resolve(results);
          }
        })
        .catch((reason) => {
          if (isRejected) return;
          isRejected = true;
          reject(reason);
        });
    });
  });
});

Promise.allSettled实现

理解 allSettled 的特性

Promise.all 不同,Promise.allSettled 会等待所有 Promise 完成(无论是兑现还是拒绝),并返回一个包含所有结果的对象数组。每个结果对象都有以下结构:

  • 对于兑现的 Promise:{ status: "fulfilled", value: <兑现值> }
  • 对于拒绝的 Promise:{ status: "rejected", reason: <拒绝原因> }

参数验证和边界处理

在参数验证和边界处理与Promise.all是相同的

js 复制代码
definePropertyConfig(Promise, "allSettled", function (iterable) {
  if (!(this === Promise)) {
    throw new TypeError("Promise.allSettled called on non-object");
  }
  if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
    throw new TypeError(
      "promises is not iterable (cannot read property Symbol(Symbol.iterator))"
    );
  }
  return new Promise((resolve) => {
    var results = [];
    var count = 0;
    var completed = 0;
    const promises = Array.from(iterable);
    count = promises.length;
    if (count === 0) {
      resolve(results);
      return;
    }
    /* 核心逻辑将在下一步实现 */
  });
});

核心逻辑实现

js 复制代码
definePropertyConfig(Promise, "allSettled", function (iterable) {
  /* 参数验证代码已省略 */
  return new Promise((resolve) => {
    /* 变量声明和边界处理省略 */
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          results[index] = { status: FULFILLED, value: value };
          completed += 1;
          if (completed === count) {
            resolve(results);
          }
        })
        .catch((reason) => {
          results[index] = { status: REJECTED, reason };
          completed += 1;
          if (completed === count) {
            resolve(results);
          }
        });
    });
  });
});

完整实现代码

js 复制代码
definePropertyConfig(Promise, "allSettled", function (iterable) {
  if (!(this === Promise)) {
    throw new TypeError("Promise.allSettled called on non-object");
  }
  if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
    throw new TypeError(
      "promises is not iterable (cannot read property Symbol(Symbol.iterator))"
    );
  }
  return new Promise((resolve) => {
    var results = [];
    var count = 0;
    var completed = 0;
    const promises = Array.from(iterable);
    count = promises.length;
    if (count === 0) {
      Promise.resolve(results);
      return;
    }
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          results[index] = { status: FULFILLED, value: value };
          completed += 1;
          if (completed === count) {
            resolve(results);
          }
        })
        .catch((reason) => {
          results[index] = { status: REJECTED, reason };
          completed += 1;
          if (completed === count) {
            resolve(results);
          }
        });
    });
  });
});

小结

通过亲手实现 Promise.allPromise.allSettled,我们深入理解了这两个重要方法的内部机制:

核心洞察

  1. 设计哲学差异

    • Promise.all 采用"全赢或全输"的策略,适合相互依赖的异步操作
    • Promise.allSettled 采用"有始有终"的策略,适合相互独立的异步操作
  2. 关键技术要点

    • 闭包的应用:通过闭包保存索引,确保结果顺序与输入顺序一致
    • Promise 规范化 :使用 Promise.resolve() 处理各种输入类型
    • 状态管理:使用计数器精确判断所有 Promise 的完成状态
    • 错误处理Promise.all 的快速失败机制 vs Promise.allSettled 的全面收集
  3. 实现模式

    • 参数验证和边界处理是健壮性的基础
    • 异步并发控制通过计数器和状态标志实现
    • 结果收集利用数组索引和闭包特性

实践价值

理解这些底层实现不仅帮助我们更自信地使用这些 API,更重要的是:

  • 当遇到复杂异步场景时,能够选择最合适的工具
  • 在调试异步问题时,能够快速定位问题根源
  • 为实现更复杂的异步控制流打下坚实基础

这两个方法的实现体现了 JavaScript 异步编程的精髓:通过简单的模式组合,构建出强大的并发处理能力。掌握它们,你就掌握了处理复杂异步场景的重要工具。

相关推荐
weixin_456904273 小时前
前端开发时npm install报错解决方案
前端·npm·node.js
东华帝君3 小时前
Object.create继承
前端
王嘉俊9253 小时前
Flask 入门:轻量级 Python Web 框架的快速上手
开发语言·前端·后端·python·flask·入门
风继续吹..3 小时前
Express.js 入门指南:从零开始构建 Web 应用
前端·javascript·express
一只小风华~3 小时前
命名视图学习笔记
前端·javascript·vue.js·笔记·学习
编程点滴3 小时前
前端项目从 Windows 到 Linux:构建失败的陷阱
linux·前端
小高0073 小时前
🎯GC 不是 “自动的” 吗?为什么还会内存泄漏?深度拆解 V8 回收机制
前端·javascript·面试
RoyLin3 小时前
V8引擎与VM模块
前端·后端·node.js
Keepreal4964 小时前
React受控组件和非受控组件的区别,用法以及常见使用场景
前端·react.js