前端实现符合Promise/A+规范的Promise

news2024/7/6 19:35:07

 🎬 岸边的风:个人主页

 🔥 个人专栏 :《 VUE 》 《 javaScript 》

⛺️ 生活的理想,就是为了理想的生活 !

在这里插入图片描述

目录

介绍:

Promise/A+规范简介

1. Promise的三种状态:

2. 状态转换:

3. Promise的基本方法:

4. 错误冒泡和异常传递:

实现Promise

步骤 1: 创建 Promise 构造函数

步骤 2: 初始化 Promise 状态和回调

步骤 3: 实现 resolve 和 reject 函数

步骤 4: 实现 then 方法

步骤 5: 处理 Promise 状态转换和回调执行

步骤 6: 解析 Promise

Promise的测试与调试

1. 安装Jest:

2. 编写单元测试:

3. 运行测试:

4. 模拟异步操作:

5. 调试Promise链:

使用Promise/A+测试套件

Promise其它API


介绍:

Promise是JavaScript中处理异步操作的重要工具之一。Promise/A+规范是一种关于Promise实现的标准,它定义了Promise的行为和方法。本文将详细介绍如何实现Promise/A+规范,让你了解Promise的工作原理并能够自己实现一个符合规范的Promise。

Promise/A+规范简介

1. Promise的三种状态:

  • pending(进行中):Promise的初始状态,表示异步操作正在执行。
  • fulfilled(已完成):异步操作成功完成,并返回一个值,称为解决值(fulfillment value)。
  • rejected(已拒绝):异步操作失败或被拒绝,并返回一个原因(reason),通常是一个错误对象。

2. 状态转换:

  • Promise的状态只能从pending转变为fulfilled或rejected,一旦转变就不可逆转。
  • 状态转换是由异步操作的结果决定的。如果异步操作成功完成,Promise的状态会转变为fulfilled;如果异步操作失败或被拒绝,Promise的状态会转变为rejected。

3. Promise的基本方法:

  • then方法:用于注册异步操作成功完成时的回调函数,并返回一个新的Promise对象。它接受两个参数:onFulfilled(可选,异步操作成功时的回调函数)和onRejected(可选,异步操作失败时的回调函数)。
  • catch方法:用于注册异步操作失败时的回调函数,并返回一个新的Promise对象。它是then方法的一个特殊形式,仅用于捕获异常。
  • finally方法:无论异步操作成功或失败,都会执行的回调函数,并返回一个新的Promise对象。它在Promise链中的最后执行,并且不接收任何参数。

4. 错误冒泡和异常传递:

  • Promise/A+规范要求Promise的错误能够被适当地捕获和处理。当一个Promise发生错误时,它会向下传播,直到找到最近的错误处理函数为止。
  • 在Promise链中的任何一个Promise发生错误,都会导致整个链上的错误处理函数被调用,以便进行错误处理和恢复。

遵循Promise/A+规范的实现应该具备上述特性,以确保一致的Promise行为和接口。这样,开发者可以编写通用的异步代码,而无需担心特定Promise实现的差异性。

实现Promise

当从零开始实现 Promise/A+ 规范的 Promise,我们需要逐步构建 Promise 的核心功能,包括状态管理、状态转换、回调处理和错误处理。

步骤 1: 创建 Promise 构造函数

首先,我们需要创建一个 Promise 构造函数,它接受一个执行器函数作为参数。执行器函数接受两个参数,即 resolve 和 reject 函数,用于控制 Promise 的状态转换。

function MyPromise(executor) {
  // TODO: 实现构造函数
}

步骤 2: 初始化 Promise 状态和回调

在构造函数中,我们需要初始化 Promise 的状态和回调数组。状态可以使用一个变量来表示,初始值为 'pending'。回调数组用于存储注册的成功和失败回调函数。

function MyPromise(executor) {
  var self = this;
  self.state = 'pending';
  self.value = undefined;
  self.reason = undefined;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  // TODO: 实现构造函数的其余部分
}

步骤 3: 实现 resolve 和 reject 函数

我们需要实现 resolve 和 reject 函数,用于将 Promise 的状态从 'pending' 转换为 'fulfilled' 或 'rejected'。resolve 函数将传递一个值来兑现 Promise,而 reject 函数将传递一个原因来拒绝 Promise。

function MyPromise(executor) {
  var self = this;
  self.state = 'pending';
  self.value = undefined;
  self.reason = undefined;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve(value) {
    if (self.state === 'pending') {
      self.state = 'fulfilled';
      self.value = value;
      self.onFulfilledCallbacks.forEach(function(callback) {
        callback(self.value);
      });
    }
  }

  function reject(reason) {
    if (self.state === 'pending') {
      self.state = 'rejected';
      self.reason = reason;
      self.onRejectedCallbacks.forEach(function(callback) {
        callback(self.reason);
      });
    }
  }

  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

步骤 4: 实现 then 方法

接下来,我们需要实现 then 方法,用于注册成功和失败的回调函数,并返回一个新的 Promise。then 方法接受两个参数:成功回调函数和失败回调函数。

function MyPromise(executor) {
  var self = this;
  self.state = 'pending';
  self.value = undefined;
  self.reason = undefined;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve(value) {
    if (self.state === 'pending') {
      self.state = 'fulfilled';
      self.value = value;
      self.onFulfilledCallbacks.forEach(function(callback) {
        callback(self.value);
      });
    }
  }

  function reject(reason) {
    if (self.state === 'pending') {
      self.state = 'rejected';
      self.reason = reason;
      self.onRejectedCallbacks.forEach(function(callback) {
        callback(self.reason);
      });
    }
  }

  try {
    executor

(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  var self = this;
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) { return value; };
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { throw reason; };

  var newPromise = new MyPromise(function(resolve, reject) {
    // TODO: 实现 then 方法的其余部分
  });

  return newPromise;
};

步骤 5: 处理 Promise 状态转换和回调执行

我们需要在 then 方法中处理 Promise 的状态转换和回调的执行。根据当前 Promise 的状态,我们可以立即执行回调函数或将回调函数添加到相应的回调数组中。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  var self = this;
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) { return value; };
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { throw reason; };

  var newPromise = new MyPromise(function(resolve, reject) {
    function handleFulfilled(value) {
      try {
        var x = onFulfilled(value);
        resolvePromise(newPromise, x, resolve, reject);
      } catch (e) {
        reject(e);
      }
    }

    function handleRejected(reason) {
      try {
        var x = onRejected(reason);
        resolvePromise(newPromise, x, resolve, reject);
      } catch (e) {
        reject(e);
      }
    }

    if (self.state === 'fulfilled') {
      setTimeout(function() {
        handleFulfilled(self.value);
      }, 0);
    } else if (self.state === 'rejected') {
      setTimeout(function() {
        handleRejected(self.reason);
      }, 0);
    } else if (self.state === 'pending') {
      self.onFulfilledCallbacks.push(function(value) {
        setTimeout(function() {
          handleFulfilled(value);
        }, 0);
      });

      self.onRejectedCallbacks.push(function(reason) {
        setTimeout(function() {
          handleRejected(reason);
        }, 0);
      });
    }
  });

  return newPromise;
};

步骤 6: 解析 Promise

最后,我们需要实现 resolvePromise 函数,用于解析 Promise。它会处理 thenable 和非 thenable 值,并根据其状态执行相应的处理。

function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    reject(new TypeError('Circular reference detected.'));
  }

  if (x && typeof x === 'object' || typeof x === 'function') {
    var called = false;

    try {
      var then = x.then;

      if (typeof then === 'function') {
        then.call(
          x,
          function(y) {
            if (!called) {
              called = true;
              resolvePromise(promise, y, resolve, reject);
            }
          },
          function(r) {
            if (!called) {
              called = true;
              reject(r);
            }
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (!called) {
        called = true;
        reject(e);
      }
    }
  } else {
    resolve(x);
  }
}

Promise的测试与调试

1. 安装Jest:

确保在项目中安装了Jest。可以使用npm或yarn进行安装。

npm install jest --save-dev

2. 编写单元测试:

在项目中创建一个测试文件,以.test.js为后缀,编写单元测试用例来验证Promise的各个功能和方法的正确性。例如,可以编写测试用例来验证状态转换、回调函数的执行、链式调用等方面的行为是否符合预期。

// promise.test.js

const { MyPromise } = require('./promise');

describe('MyPromise', () => {
  it('should fulfill with correct value', () => {
    const promise = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve('success');
      }, 100);
    });

    return promise.then((value) => {
      expect(value).toBe('success');
    });
  });

  it('should reject with correct reason', () => {
    const promise = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('failure'));
      }, 100);
    });

    return promise.catch((reason) => {
      expect(reason).toBeInstanceOf(Error);
      expect(reason.message).toBe('failure');
    });
  });

  // 更多测试用例...
});

3. 运行测试:

使用Jest运行编写的测试用例,执行Promise的测试。

npx jest

4. 模拟异步操作:

使用setTimeout函数或Promise.resolve等方法来模拟异步操作,并确保Promise在正确的时机进行状态转换和回调函数的执行。例如,可以使用setTimeout来模拟异步操作的完成。

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success');
  }, 100);
});

promise.then((value) => {
  console.log(value); // 输出: success
});

5. 调试Promise链:

在开发过程中,可能会遇到Promise链上的问题,如回调函数不执行、结果不符合预期等。可以使用console.logdebugger语句来打印中间变量的值,或者使用Jest的调试功能来逐步跟踪Promise链的执行过程。

const promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success');
  }, 100);
});

const promise2 = promise1.then((value) => {
  console.log(value); // 输出: success
  return value.toUpperCase();
});

promise2.then((value) => {
  console.log(value); // 输出: SUCCESS
});

可以使用Jest的--inspect参数进行调试,或者在代码中添加debugger语句来触发断点。

npx jest --inspect

使用Promise/A+测试套件

使用Promise/A+测试套件是确保Promise实现符合规范的重要步骤。Promise/A+测试套件是一组针对Promise实现的测试用例,可用于验证Promise是否符合Promise/A+规范的要求。

  1. 下载Promise/A+测试套件: 首先,从Promise/A+官方的GitHub仓库(https://github.com/promises-aplus/promises-tests)下载Promise/A+测试套件的代码。将其保存到项目的测试目录中。open in new window

  2. 集成测试套件: 将Promise/A+测试套件集成到项目的测试环境中,确保可以运行测试套件并获得结果。

  3. 实现Promise接口: 根据Promise/A+规范的要求,实现一个符合规范的Promise类。确保Promise类的行为和接口与规范一致。

  4. 运行测试套件: 使用测试框架(如Mocha、Jest等)运行Promise/A+测试套件。在测试套件的配置中,指定测试文件为Promise/A+测试套件的入口文件。

  5. 验证结果: 查看测试套件的运行结果。如果所有的测试用例都通过,表示Promise实现符合Promise/A+规范。如果有测试用例失败,根据测试结果来调试和修复Promise实现中的问题。

下面是一个示例,展示如何使用Promise/A+测试套件进行测试:

// 安装Promise/A+测试套件
npm install promises-aplus-tests --save-dev

// 集成Promise/A+测试套件到测试环境中
const promisesAplusTests = require('promises-aplus-tests');
const { MyPromise } = require('./promise');

// 运行Promise/A+测试套件
promisesAplusTests(MyPromise, function (err) {
  // 测试完成后的回调函数
  if (err) {
    console.error('Promise/A+测试失败:', err);
  } else {
    console.log('Promise/A+测试通过!');
  }
});

在上述代码中,MyPromise是自己实现的Promise类。通过将MyPromise传递给promisesAplusTests函数,将Promise类集成到Promise/A+测试套件中。运行测试后,将会得到测试结果。

通过使用Promise/A+测试套件,可以验证自己实现的Promise是否符合Promise/A+规范的要求,确保Promise的行为和接口的一致性。

Promise其它API

要实现Promise.allPromise.race等其他API,可以根据Promise的规范和功能需求来编写相应的代码。

  1. 实现Promise.all: Promise.all方法接收一个可迭代对象(如数组或类数组对象),并返回一个新的Promise,该Promise在所有输入的Promise都成功完成时才会成功,否则将会失败。返回的Promise的解决值是一个由所有输入Promise解决值组成的数组。

Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completedCount = 0;

    const checkCompletion = () => {
      if (completedCount === promises.length) {
        resolve(results);
      }
    };

    for (let i = 0; i < promises.length; i++) {
      promises[i]
        .then((value) => {
          results[i] = value;
          completedCount++;
          checkCompletion();
        })
        .catch((reason) => {
          reject(reason);
        });
    }

    if (promises.length === 0) {
      resolve(results);
    }
  });
};

使用示例:

const promise1 = new Promise((resolve) => setTimeout(() => resolve(1), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1500));

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // 输出: [1, 2, 3]
  })
  .catch((reason) => {
    console.error(reason);
  });
  1. 实现Promise.race: Promise.race方法接收一个可迭代对象(如数组或类数组对象),并返回一个新的Promise,该Promise将与最先解决或拒绝的输入Promise具有相同的状态。

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i]
        .then((value) => {
          resolve(value);
        })
        .catch((reason) => {
          reject(reason);
        });
    }
  });
};

使用示例:

const promise1 = new Promise((resolve) => setTimeout(() => resolve(1), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1500));

Promise.race([promise1, promise2, promise3])
  .then((value) => {
    console.log(value); // 输出: 1
  })
  .catch((reason) => {
    console.error(reason);
  });

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1012760.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

游戏联运和游戏代理的区别

游戏联运和游戏代理是游戏行业中两种不同的合作模式&#xff0c;它们有一些明显的区别&#xff1a; 合作性质&#xff1a; 游戏联运&#xff1a;游戏联运是多家游戏发行商或开发商之间的合作&#xff0c;它们合作共同推广、发布和运营游戏&#xff0c;每个合作方负责自己的一…

许可分析 license分析 第二章

许可分析是指对软件许可证进行详细的分析和评估&#xff0c;以了解组织内部对软件许可的需求和使用情况。通过许可分析&#xff0c;可以帮助组织更好地管理和优化软件许可证的使用。以下是一些可能的许可分析方法和步骤&#xff1a; 软件许可证更新和续订&#xff1a;及时跟踪和…

QT-day1

实现华清远见登陆界面 #include "mywnd.h" #include <iostream> #include <QDebug> #include <QPushButton> #include <QLineEdit> #include <QLabel>MyWnd::MyWnd(QWidget *parent): QWidget(parent) {//设置固定窗口大小长400&…

ToDoList待办事件(Vue实现)详解

组件化的编码流程&#xff08;通用&#xff09; 实现静态组件&#xff1a;抽取组件&#xff0c;使用组件实现静态页面效果展示动态数据 数据的类型名称 数据保存的组件位置交互-从绑定事件监听开始 实现静态页面 组件按照功能划分 也可以按照其他的划分&#xff0c;我是按照…

Mysql高级——索引(2)

常见索引 索引分类 在MySQL数据库&#xff0c;将索引的具体类型主要分为以下几类&#xff1a;主键索引、唯一索引、常规索引、全文索引。 分类含义特点关键字主键索引针对于表中主键创建的索引默认自动创建, 只能有一个PRIMARY唯一索引避免同一个表中某数据列中的值重复可以…

Linux系统编程(二):文件和目录

参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 系统编程 1. 文件存储 一个文件主要由两部分组成&#xff0c;dentry (目录项) 和 Inode所谓的删除文件&#xff0c;就是删除 Inode&#xff0c;但数据其实还是在硬盘上&#xff0c;以后会覆盖掉 1.1 Inode 其本质为结构体…

使用Jmeter+ant进行接口自动化测试(数据驱动)

最近在做接口测试&#xff0c;因为公司有使用jmeter做接口测试的相关培训资料&#xff0c;所以还是先选择使用jmeter来批量管理接口&#xff0c;进行自动化测试。话不多说&#xff0c;进入正题&#xff1a; 1.使用csv文件保存接口测试用例&#xff0c;方便后期对接口进行维护&…

netperf 测试时延和吞吐

一、Netperf是一种网络性能测试工具&#xff0c;主要基于TCP或UDP的传输。可以测量TCP和UDP传输的吞吐量、时延、CPU 占用率等性能参数。Netperf测试结果所反映的是一个系统能够以多块的速度向另一个系统发送数据&#xff0c;以及另一个系统能够以多块的速度接收数据。 二、打…

数据结构与算法:树

目录 树 定义 结构 二叉树 定义 结构 形式 满二叉树 完全二叉树 存储 链式存储结构 数组 孩子节点 父节点 应用 查找 维持相对顺序 遍历 深度优先遍历 前序遍历 中序遍历 后序遍历 广度优先遍历 层序遍历 二叉堆 定义 自我调整 操作 插入加点 删…

【VUE异常】el-popconfirm失效,@confirm事件不生效,点击没有任何反应,刷新页面才能点击

el-popconfirm失效&#xff0c;confirm事件不生效&#xff0c;点击没有任何反应&#xff0c;刷新页面才能点击 一、背景描述二、原因分析三、解决方案3.1 方案一&#xff1a;使用onConfirm3.2 方案二&#xff1a;confirm与onConfirm同时使用3.3 方案三&#xff1a;el-popconfir…

thinkphp:查询本周中每天中日期的数据,查询今年中每个月的数据,查询近五年每年的总数据

一、查询本周中每天中日期的数据 结果&#xff1a; 以今天2023-09-14为例&#xff0c;这一周为2023-09-11~2023-09-07 代码 后端thinkphp: //查询本周每天的的总金额数 //获取本周的起始日期和结束日期 $weekStart date(Y-m-d, strtotime(this week Monday)); $weekEnd …

Tomcat配置敏感信息屏蔽

一、tomcat敏感信息屏蔽 tomcat的错误信息会吧一些敏感信息给暴露出来比如版本号。 解决方案 在tomcat的conf文件下配置server.xml的 < Host > 标签 <Valve className"org.apache.catalina.valves.ErrorReportValve" showReport"false" showS…

leetcode92. 反转链表 II(java)

反转链表 II 题目描述哨兵技巧代码演示 题目描述 难度 - 中等 leetcode92. 反转链表 II 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例1&#xff…

老板要我开发一个简单的工作流引擎-读后感与补充

概述 最近读了一篇《老板要我开发一个简单的工作流引擎》 幽默风趣&#xff0c;干货较多&#xff0c;作为流程引擎的设计者、开发者、探索者&#xff0c;写的很好&#xff0c;合计自己的理解&#xff0c;对每个功能补充说明&#xff0c;对于流程引擎的应用场景&#xff0c;做出…

CoinW TOKEN2049 After Party落幕,分享如何在加密寒冬保持增长

CoinW, 全球领先的数字资产交易平台&#xff0c;在TOKEN2049新加坡期间举办的After Party&#xff0c;于当地时间13日晚落幕。Sui、1inch、Hacken、TinyTrader、AI Analysis等数十个项目方现场带来主题分享。CoinGecko、Cointelegraph、Coin Time、Coinlive、ODAILY、BlockBeat…

使用 Python 和机器学习掌握爬虫和情感分析

在本教程中&#xff0c;我们将抓取一个网站并使用自然语言处理来分析文本数据。 最终结果将是对网站内容的情感分析。以下是我们将遵循的步骤&#xff1a; 项目范围所需的库了解网页抓取抓取网站文本清理和预处理使用机器学习进行情感分析最后结果 一、项目范围 该项目的目…

Bean的管理

配置优先级 Bean管理 获取bean 默认情况下&#xff0c;Spring项目启动时&#xff0c;会把bean多创建好放在IOC容器中&#xff0c;如果想要主动获取这些bean&#xff0c;可以通过如下方式&#xff1a; bean作用域 Spring支持五种作用域&#xff0c;后三种在Web环境才生效&…

十、MySql的索引(重点)

文章目录 一、定义二、常见分类&#xff08;一&#xff09;案例 三、 认识磁盘&#xff08;一&#xff09;MySQL与存储&#xff08;二&#xff09;扇区&#xff08;三&#xff09;定位扇区&#xff08;四&#xff09;结论&#xff08;五&#xff09;磁盘随机访问(Random Access…

EasyX图形化界面

这里写目录标题 EasyX绘制简单的图形化窗口窗口坐标设置窗口属性实现基本绘图功能贴图原样贴图透明贴图认识素材 代码步骤 按键交互阻塞按键 鼠标交互 EasyX 绘制简单的图形化窗口 代码示例&#xff1a; while&#xff08;1&#xff09;&#xff1b; 可以防止闪屏 窗口坐标 …

初见QT,控件的基本应用,实现简单登录窗口

窗口实现代码 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//窗口设置this->setFixedSize(538, 373); //固定窗口大小this->setWindowIcon(QIcon("G:\\QT_Icon\\windos_icon2.png"))…