JS Promise 实现原理 – 手写符合 Promises/A+ 规范的异步解决方案

Promise通过状态机解决异步编程中的回调地狱问题,其核心是实现pending、fulfilled、rejected三种状态的不可逆转换及then方法链式调用;需遵循Promises/A+规范,重点处理resolvePromise过程以支持嵌套与异常捕获,并通过官方测试套件验证兼容性。

js promise 实现原理 - 手写符合 promises/a+ 规范的异步解决方案

Promise 的核心在于解决 JavaScript 异步编程中的回调地狱问题,并提供更优雅的错误处理机制。本质上,Promise 是一种状态机,它代表了一个异步操作的最终完成(或失败)及其结果值。手写 Promise 意味着要理解并实现这种状态转换和结果传递的机制。

解决方案

实现一个符合 Promises/A+ 规范的 Promise,需要关注以下几个关键点:

状态(States): Promise 必须有三种状态:

pending

(进行中)、

fulfilled

(已成功)和

rejected

(已失败)。状态一旦改变,就不能再次改变。

then

方法:

then

方法用于注册当 Promise 状态改变时要执行的回调函数。它可以被多次调用,并且必须返回一个新的 Promise。

解决过程(Resolution Procedure): 这是 Promise 实现中最复杂的部分,它定义了如何将 Promise 的状态从

pending

转换为

fulfilled

rejected

。特别需要处理

then

方法返回的 Promise 的解决,以及 Promise 链中的异常捕获。

一个简单的 Promise 实现的骨架如下:

function MyPromise(executor) {  let state = 'pending';  let value = undefined;  let reason = undefined;  let onFulfilledCallbacks = [];  let onRejectedCallbacks = [];  function resolve(val) {    if (state === 'pending') {      state = 'fulfilled';      value = val;      onFulfilledCallbacks.forEach(callback => callback(value));    }  }  function reject(rea) {    if (state === 'pending') {      state = 'rejected';      reason = rea;      onRejectedCallbacks.forEach(callback => callback(reason));    }  }  try {    executor(resolve, reject);  } catch (err) {    reject(err);  }  this.then = function(onFulfilled, onRejected) {    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };    let promise2 = new MyPromise((resolve, reject) => {      if (state === 'fulfilled') {        setTimeout(() => { // 确保 promise2 已经被创建          try {            let x = onFulfilled(value);            resolvePromise(promise2, x, resolve, reject);          } catch (e) {            reject(e);          }        }, 0);      }      if (state === 'rejected') {        setTimeout(() => {          try {            let x = onRejected(reason);            resolvePromise(promise2, x, resolve, reject);          } catch (e) {            reject(e);          }        }, 0);      }      if (state === 'pending') {        onFulfilledCallbacks.push(() => {          setTimeout(() => {            try {              let x = onFulfilled(value);              resolvePromise(promise2, x, resolve, reject);            } catch (e) {              reject(e);            }          }, 0);        });        onRejectedCallbacks.push(() => {          setTimeout(() => {            try {              let x = onRejected(reason);              resolvePromise(promise2, x, resolve, reject);            } catch (e) {              reject(e);            }          }, 0);        });      }    });    return promise2;  }}function resolvePromise(promise2, x, resolve, reject) {  if (promise2 === x) {    return reject(new TypeError('Chaining cycle detected for promise #'))  }  let called;  if ((typeof x === 'object' && x != null) || typeof x === 'function') {    try {      let then = x.then;      if (typeof then === 'function') {        then.call(x, y => {          if (called) return;          called = true;          resolvePromise(promise2, y, resolve, reject);        }, r => {          if (called) return;          called = true;          reject(r);        })      } else {        resolve(x);      }    } catch (e) {      if (called) return;      called = true;      reject(e);    }  } else {    resolve(x);  }}

这段代码展示了一个基础的 Promise 实现,包括状态管理、

then

方法和异步执行。

resolvePromise

函数是解决过程的核心,它递归地处理 Promise 的嵌套,确保 Promise 链的正确执行。

如何处理 Promise 中的异常?

Promise 的异常处理主要依赖于

reject

函数和

then

方法的第二个参数(

onRejected

)。如果在

executor

函数或

onFulfilled

回调中抛出异常,Promise 的状态会变为

rejected

,并且异常会被传递给

onRejected

回调。如果没有提供

onRejected

回调,异常会被传递到 Promise 链的下一个

onRejected

回调,直到被捕获。

此外,可以使用

catch

方法来捕获 Promise 链中的所有异常。

catch

方法实际上是

then(null, onRejected)

的语法糖。

new MyPromise((resolve, reject) => {  throw new Error('Something went wrong!');}).then(() => {  // This will not be executed}).catch(error => {  console.error('Caught an error:', error); // Output: Caught an error: Error: Something went wrong!});

手写 Promise 时需要注意哪些性能优化?

性能优化是手写 Promise 时需要考虑的重要因素。以下是一些常见的优化策略:

避免不必要的异步操作: 如果 Promise 的结果已经可用,可以直接同步地调用

onFulfilled

onRejected

回调,而不需要使用

setTimeout

或其他异步机制。

减少 Promise 的创建: 频繁地创建 Promise 会增加内存消耗和垃圾回收的压力。尽量重用 Promise 实例,避免在循环或高频调用的函数中创建 Promise。

使用微任务队列: 使用

MutationObserver

process.nextTick

等微任务队列来调度回调函数,可以比

setTimeout

更快地执行回调,提高 Promise 的响应速度。但要注意,过度使用微任务队列可能会导致 UI 渲染阻塞。

避免深层 Promise 嵌套: 深层 Promise 嵌套会增加代码的复杂性和调试难度,并且可能导致性能问题。尽量使用

async/await

语法糖来简化异步代码,减少 Promise 的嵌套。

如何测试手写的 Promise 是否符合 Promises/A+ 规范?

要确保手写的 Promise 符合 Promises/A+ 规范,可以使用 Promises/A+ 官方提供的测试套件。该测试套件包含一系列测试用例,可以验证 Promise 的行为是否符合规范。

安装测试套件: 使用 npm 安装

promises-aplus-tests

包。

npm install promises-aplus-tests --save-dev

编写测试适配器: 创建一个测试适配器,将手写的 Promise 暴露给测试套件。

// adapter.jsconst MyPromise = require('./my-promise'); // 替换为你的 Promise 实现module.exports = {  deferred: function() {    let dfd = {};    dfd.promise = new MyPromise((resolve, reject) => {      dfd.resolve = resolve;      dfd.reject = reject;    });    return dfd;  },  resolved: MyPromise.resolve,  rejected: MyPromise.reject};

运行测试: 使用

promises-aplus-tests

命令运行测试,并指定测试适配器。

npx promises-aplus-tests adapter.js

测试套件会运行所有测试用例,并输出测试结果。如果所有测试都通过,则说明手写的 Promise 符合 Promises/A+ 规范。

请注意,这只是一个简化的实现,实际的 Promise 实现需要考虑更多的细节和边界情况,例如处理 Promise 的循环引用、处理

then

方法的返回值等。建议参考 Promises/A+ 规范和成熟的 Promise 库(例如 Bluebird)的源码,以获得更完整的理解。

以上就是JS Promise 实现原理 – 手写符合 Promises/A+ 规范的异步解决方案的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1520557.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 13:25:55
下一篇 2025年12月20日 13:26:04

相关推荐

  • JavaScript中函数作为参数的执行机制解析

    javascript函数是第一类对象,可作为参数传递给其他函数。其执行方式取决于接收函数内部逻辑:有些函数仅将其作为数据处理(如`console.log`),而另一些则会调用它作为回调(如`array.prototype.sort()`)。理解这一机制对于编写高效的异步代码和高阶函数至关重要。 在J…

    2025年12月20日
    000
  • JavaScript中函数作为参数的运行机制:高阶函数与回调的深度解析

    javascript中的函数是“一等公民”,可以作为参数传递给其他函数。这种传递仅仅是传递函数引用,而非立即执行。函数的实际执行取决于接收函数(高阶函数)的内部逻辑,它可能在特定时机调用这个作为参数的函数(回调函数),也可能仅将其视为普通数据进行处理。理解这一机制是掌握javascript异步编程和…

    2025年12月20日
    000
  • 深入理解 V8 Isolate::Scope:避免跨函数调用中的访问冲突

    `v8::isolate::sc++ope` 是 v8 引擎中用于管理隔离区执行上下文的关键机制,它采用 c++ raii 模式。本文将深入探讨 `isolate::scope` 的生命周期特性及其在多函数调用场景中的重要性。通过分析其作用域行为,解释为何在每次与 v8 隔离区交互的函数中都需要显式…

    2025年12月20日
    000
  • 深入理解 V8 Isolate::Scope:C++ 生命周期与上下文管理

    `v8::isolate::sc++ope` 用于在 c++ 应用程序中激活 v8 `isolate` 的上下文,确保 v8 操作在一个有效的运行时环境中执行。其核心在于 c++ 局部对象的生命周期管理:当 `isolate::scope` 对象所在的 c++ 代码块结束时,该对象即被销毁,其激活的…

    2025年12月20日
    000
  • 如何在React应用中实现条件式导航到详情页

    本教程探讨在React应用中,当用户导航到列表页时,如何根据数据量实现条件式导航:若数据仅一条,则直接跳转至详情页;若多于一条,则展示列表。文章详细介绍了如何通过`react-router-dom`配置独立的列表和详情路由,并利用`useNavigate`钩子在列表组件中实现条件重定向,从而避免常见…

    2025年12月20日
    000
  • 使用 useParams 时 useEffect 意外执行的解决方法

    本文旨在解决在使用 React Router 的 `useParams` 钩子时,由于依赖项设置不当导致 `useEffect` 意外执行的问题。通过提取 `params` 对象中的特定属性作为依赖项,并添加必要的依赖项,可以避免不必要的副作用,提高组件的性能和可预测性。 在使用 React Rou…

    2025年12月20日
    000
  • ExtJS Grid与Store数据加载:常见错误排查与最佳实践

    本教程深入探讨ExtJS数据网格(Grid)与数据存储(Store)的数据加载机制。文章将重点解析`dataIndex`与API响应字段不匹配、Store配置不当等常见问题,并提供解决方案。同时,将介绍Store的定义方式、`autoLoad`属性的使用以及在ExtJS应用中管理数据存储的最佳实践,…

    2025年12月20日
    000
  • Vue 3 组件非元素根节点指令警告:原理与解决之道

    在Vue 3升级或开发过程中,开发者可能会遇到“Runtime directive used on component with non-element root node”警告。此警告表明组件模板的根节点不是单一元素,导致指令无法按预期工作。核心解决方案是确保组件模板只有一个顶级包装元素,如 ,以…

    2025年12月20日
    000
  • JavaScript中函数作为参数的执行机制与回调函数详解

    本文深入探讨了javascript中函数作为一等公民的特性,以及它们如何作为参数被传递和执行。我们将详细解析当一个函数被作为参数传入另一个函数时,其行为如何由接收函数内部逻辑决定,并通过`console.log`和`array.prototype.sort`等具体示例,区分函数被视为数据值与被实际执…

    2025年12月20日
    000
  • Vue 3中Proxy对象的数据访问与组件通信实践

    本文旨在解决vue 3应用中通过异步请求获取数据并将其作为prop传递给子组件时,遇到的数据以`proxy(object)`形式显示且难以直接访问的问题。我们将深入探讨vue 3的响应式原理、异步数据处理的最佳实践,以及父子组件间数据传递的正确姿势,通过代码示例和详细解释,确保开发者能够顺畅地访问和…

    2025年12月20日
    000
  • 如何在JavaScript中判断两个日期是否连续

    本文将详细介绍如何在JavaScript中准确判断两个日期(如`startDate`和`endDate`)是否连续,即`endDate`是否恰好是`startDate`的下一天。我们将通过比较日期的时间戳并考虑一天的毫秒数差异来实现这一逻辑,这在处理日历或预订系统中的单日预订场景时尤为实用。 日期连…

    2025年12月20日
    000
  • 在Google Pie Chart切片中添加百分比符号的专业指南

    在数据可视化中,尤其是在使用饼图(pie chart)展示比例数据时,直观地显示百分比是一个常见的需求。google charts是一个功能强大的javascript库,用于创建各种交互式图表。然而,直接在数据源(如sql查询)中拼接百分比符号,并不能被google charts正确解析为数值进行图…

    好文分享 2025年12月20日
    000
  • 在React Native中动态传递图片路径作为Prop的指南

    本教程旨在解决react native中将图片路径作为prop传递时遇到的常见问题。文章详细解释了`image`组件处理本地(打包)和远程图片的不同机制,分析了动态`require()`和不完整uri的失败原因。核心内容是指导开发者如何正确构建远程图片的完整uri,以及如何通过映射处理动态本地图片,…

    2025年12月20日
    000
  • 使用 useParams 时 useEffect 意外执行:依赖项问题及解决方案

    本文旨在解决在使用 React Router 的 `useParams` 钩子时,由于依赖项设置不当导致 `useEffect` 意外执行的问题。通过分析问题原因,并提供修改后的代码示例,帮助开发者避免此类错误,确保 `useEffect` 在预期的时间执行。 在使用 React Router 的 …

    2025年12月20日
    000
  • WordPress中JavaScript类与视差效果的集成与性能优化

    本文旨在解决在wordpress网站中集成javascript类时遇到的实例化和性能问题,特别是针对视差动画等动态效果。我们将探讨如何通过重构javascript类、采用工厂函数模式来管理实例创建,并优化滚动事件监听以提升网站性能和用户体验。 在WordPress网站开发中,利用JavaScript…

    2025年12月20日
    000
  • 安全地在客户端创建Stripe支付链接:可行性分析与替代方案

    本文探讨了在纯客户端环境下,不暴露Stripe密钥的前提下创建Stripe支付链接的可行性。由于Stripe API的安全机制,直接在客户端使用密钥存在安全风险。本文分析了该问题的本质,并提供了两种替代方案:预先生成固定支付链接或搭建后端服务动态生成。同时,建议根据具体业务场景考虑使用Checkou…

    2025年12月20日
    000
  • 如何在不暴露密钥的情况下,在客户端创建 Stripe Payment Link

    本文介绍了在纯静态网站环境下,如何利用 Stripe Payment Link 实现商品售卖,并着重讨论了在不暴露 Stripe 密钥的前提下,客户端创建 Payment Link 的可行性。分析了直接在客户端使用密钥的风险,并提出了预先生成 Payment Link 或使用后端服务动态生成 Pay…

    2025年12月20日
    000
  • Web Components如何与现代前端框架协同工作?

    Web Components 与现代前端框架可协同工作,实现跨项目复用。1. React 中需注意属性传递、事件监听及警告规避;2. Vue 3 可通过配置识别自定义元素,支持属性绑定与事件通信;3. Angular 天然兼容 Web Components,可直接使用并利用 Shadow DOM 隔…

    2025年12月20日
    000
  • JavaScript模板引擎设计

    核心目标是将数据与模板结合生成HTML,通过解析语法、变量替换和逻辑控制实现渲染。采用{{}}插值和执行代码的语法设计,编译模板为JavaScript函数,支持字符串拼接输出;引入转义机制防止XSS,区分转义与非转义插值;利用缓存避免重复编译提升性能;最终实现轻量、安全、高效的模板引擎。 /g, &…

    2025年12月20日
    000
  • 基于多个数组数据计算结果排序的 React 教程

    本文档旨在解决在 React 应用中,如何根据两个独立数组中的数据计算结果对数据进行排序的问题。通过合并数据或使用映射对象,可以实现在排序时访问两个数组的信息,从而实现复杂的排序逻辑。本文将提供详细的代码示例和步骤,帮助开发者理解和应用这些方法。 在 React 应用中,经常会遇到需要根据多个数据源…

    2025年12月20日
    000

发表回复

登录后才能评论
关注微信