【手写 Promise 源码】第八篇 - 完善 Promise 并通过 promise-aplus-tests 测试

news2024/11/19 7:26:37

一,前言

上一篇,实现 Promise 对返回值 x 各种情况的分析和处理,主要涉及以下几个点:

  • 回顾了相关的 Promise A+ 规范内容;
  • 根据 Promise A+ 规范描述和要求,实现了核心解析方法:resolvePromise;

本篇,继续对 Promise 进行完善并通过 promise-aplus-tests 测试;


二,当前 Promise 的问题

当前版本 Promise 源码,已基本实现了 Promise A+ 规范中的全部要求;

但在实际使用中,以下两种情况仍与原生 Promise 表现不一致:

  • 1,Promise 直接 resolve 一个 Promise;
  • 2,Promise 的 then 中方法返回 promise,这个 promise 又继续 resolve 了一个 promise;

备注

  • 有人会说:1 和 2 两种情况好像是一个事啊?都是 resolve 中又返回了 promise;
  • 先说一下区别,后边详细分析:
    • resolve 中直接返回的 promise,对应源码中的 value;
    • then 中返回的 promise 中的 resolve 中的 promise,对应源码中的 y;

三,情况 1:Promise 直接 resolve 一个 Promise

1,提出问题

如果在 Promise 中的 resolve 传入一个 Promise,结果是怎样的?

new Promise((resolve,reject)=>{
  resolve(new Promise((resolve,reject)=>{
    resolve(100)
  }))
}).then(data=>{
  console.log(data)
})

正常情况下 resolve(100),then 成功回调中的 data 就是 100,所以 data 就是 promise,就是 100;

2,测试原生的 Promise

原生 Promise 返回 100,与预期相符

3,测试手写的 Promise

与原生 Promise 表现不一致,返回 DULFILLED 成功态 promise 对象:

Promise {
  state: 'DULFILLED',
  value: 100,
  reason: undefined,
  onResolvedCallbacks: [],
  onRejectedCallbacks: []
}

4,问题分析

对比手写的 Promise 和原生 Promise 的执行结果,问题出在哪?

new Promise((resolve,reject)=>{
  resolve(new Promise((resolve,reject)=>{  // 这个 promise 就是 value
    resolve(100) // 成功结果
  }))
}).then(data=>{  // 这个 data 就是上边的 promise,所以是 Pending 态
  console.log(data)
})

分析代码的执行流程:

  • 通过 new Promise 创建 promise1 实例,executor1 执行器函数被立即执行;
  • 在 executor1 中,执行了resolve(new Promise),又创建 promise2 实例,executor2 被立即执行;
  • 在 executor2 中,由于没有异步操作,所以继续执行了resolve(100)
  • Promise 源码处理:在 resolve 方法中,存储了 value 值为 100、设置了 promise2 状态为成功态、执行了成功回调(未收集的空数组);
  • 调用 then 方法;
  • Promise 源码处理:then 方法内部创建 promise3,此时,promise2 为 DULFILLED 成功态,执行 onFulfilled 成功回调处理,并将 promise2 作为 data 传给 then的成功回调,也就是 data

这就导致了返回结果是一个 DULFILLED 成功态的 promise 对象;

问题原因:

源码中没有考虑到 resolve(value) 中 value 有可能是 promise 的情况;

    const reslove = (value) =>{
      if(this.state === PENDING){
        this.value = value // value 有可能是一个 promise
        this.state = DULFILLED;
        this.onResolvedCallbacks.forEach(fn=>fn())
      }
    }

解决方案:

添加判断,如果 value 是一个 promise,调用 then 让其执行;

5,代码实现

判断value 是 Promise 类型(必须是自己 Promise 才可以),调用 then 并传入resolve、reject(成功将会调用 resolve;失败将会调用 reject),返回最终 promise 的执行结果;

const reslove = (value) => { 
  // value 是自己实现的 Promise,就调用 then,返回最终结果
  if(value instanceof Promise){
    return value.then(reslove, reject)
  }

  if (this.state === PENDING) {
    this.value = value
    this.state = DULFILLED;
    this.onResolvedCallbacks.forEach(fn => fn())
  }
}

这样,reslove 了一个 promise 时,在源码 reslove 方法中,就会调用它的 then 方法,返回 promise 执行后的结果;

备注:Promise A+ 规范中未对此情况进行说明;


四,情况 2:then 返回的 promise 中,resolve 了 promise(有点儿绕嘴)

1,提出问题

在上一篇中,当 Promise.then 返回 promise 时,核心逻辑resolvePromise如下:

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('发生错误'))
  }
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then;
      if(typeof then === 'function'){
        // 调用 promise 的 then 方法
        then.call(x, y => {
          resolve(y)     // 更新 promise2 的状态
        }, r => {
          reject(r)
        });
      }else{
        resolve(x)
      }
    } catch (e) {
      reject(e);
    }
  } else {
    resolve(x)
  }
}
  • then 中方法的返回值 y 有可能是 promise,形成 promise 的嵌套;

情况 1 已经处理了 resolve 中是 promise 的情况,这里还会有问题吗?

一般是没有问题的,但这里的 promise 有可能是其他人实现的 Promise;
(情况 1 中的 Promise 必须是自己实现的)

2,测试原生的 Promise

let promise2 = new Promise((resolve, reject) => {
  resolve(200)
}).then(data => {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      resolve(new Promise((resolve, reject) => {
        setTimeout(()=>{
          resolve(data)
        }, 1000)
      }))
    }, 1000)
  })
})
promise2.then(data => {
  console.log(data)
})

// 200

3,测试手写的 Promise

输出结果:
Promise {
  state: 'PENDING',
  value: undefined,
  reason: undefined,
  onResolvedCallbacks: [],
  onRejectedCallbacks: []
}

4,问题分析

let p = new Promise((resolve, reject) => {
  // 1,执行器函数被立即执行
  resolve(200)
}).then(data => {
  // 2,进入成功回调处理,返回Promise
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      resolve(new Promise((resolve, reject) => {
        setTimeout(()=>{
          resolve(data)
        }, 1000)
      }))
    }, 1000)
  })
})
p.then(data => {
  console.log(data)
})

分析代码的执行流程:

  • new Promise 创建实例,executor 执行器函数被立即执行,resolve(200)被执行;
  • Promise 源码处理:在 resolve 方法中,存储了 value 值为 200、设置了 promise 状态为成功态、执行了成功回调(未收集的空数组);
  • 调用 then 方法;
  • Promise 源码处理:then 方法内部创建 promise2-1,此时,p 为 DULFILLED 态,通过 setTimeout 创建宏任务1(获取 onFulfilled 返回值 x,调用resolvePromise 统一解析处理返回值 x),延迟到一下事件循环中处理;
  • 执行p.then;
  • Promise 源码处理:then 方法内部创建新的 promise2-2,由于上个 promise2-1 创建的宏任务 1 尚未执行,所以,上个 promise2-1 的状态仍为 PENDING 态,因此,会对p.then中的成功/失败回调函数进行收集;
  • 至此,代码执行完毕,也就是第一轮宏任务执行完毕了;
  • 宏任务 1 被执行,进入代码中第一个 then 的成功处理,data 为 200,返回值是一个 P
    promise;
  • Promise 源码处理:内部拿到 then 的返回值 x,调用 resolvePromise 进行统一的解析处理,由于此处 x 是 promise,所以,在 resolvePromise 中会调用它的 then 方法;
  • 但是,由于这个 promise(也就是 x),内部存在异步操作(setTimeout 1秒),所以当调用这个 promise(也就是 x)的 then 方法时,这个 promise(也就是 x)的状态仍为 PENDING 态
  • 一秒后,执行 resolve(new Promise),此时,resolve 中的 promise 就是源码中的 y;

问题原因

  • 手写的源码只处理了 then 中方法的返回值 x;但 resolve 中为 promise 的情况没有处理;(resolve 中的 promise 就是源码中的 y)
  • 由于 y 是一个 promise,所以直接 resolve(y) ,即 then 中的 data,得到的就是 一个 PENDING 态的 promise 实例;

解决方案

  • 由于 y 有可能是一个 promise 对象,所以对 y 使用 resolvePromise 进行递归处理,直到 y 为普通值为止;

相关 Promise A+规范内容:

image.png

5,代码实现

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('发生错误'))
  }
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then;
      if(typeof then === 'function'){
        then.call(x, y => {
          // resolve(y)
          resolvePromise(promise2, y, resolve, reject) // 递归处理 y
        }, r => {
          reject(r)
        });
      }else{
        resolve(x)
      }
    } catch (e) {
      reject(e);
    }
  } else {
    resolve(x)
  }
}

三,Promise 的兼容处理

在开发中,所使用的 Promise 不一定都是原生的或我们自己实现的;

任何框架/库/开发者都有可能根据 promise A+ 规范实现一个 Promise;

为了确保使用这些 Promise 不会导致项目问题,需要对 Promise 进行兼容处理;

  • 备注
    理论上,严格按照 Promise A+ 规范实现的 Promise 是无需进行兼容处理的;所以,兼容处理主要用于弥补实现上的 Promise 漏洞和不严谨;

  • 比如:

let Promise1; // 自己实现的 Promise
let promise2;// 别人实现的 Promise

promise = new Promise1((resolve, reject) => {
  resolve(1);
}).then(() => {
  return new Promise2();
})

Promise 的兼容处理,有以下几处:

  • 每个 Promise 实例的状态只能被改变一次;

四,promise-aplus-tests 测试

promise-aplus-tests 用于测试自己实现的 Promise 是否符合 Promise A+ 规范;

1,安装 promise-aplus-tests

npm install promises-aplus-tests -g

2,添加测试入口-创建延迟对象

  • 创建延迟对象Promise.deferred

延迟对象:一个具有“延时”效果的对象(因为对象中含有 Promise);

通过Promise.deferred方法,测试当前 dfd 对象上的 Promise 实现是否符合 Promise A+ 规范要求;

Promise.deferred = function(){
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}

这里使用了延迟对象,可以减少一层嵌套;

  • Promise.deferred方法中,创建了一个 promise 对象dfd.promise
  • 相当于将 resolve/reject 绑定到 dfd.resolve/dfd.reject
  • promise 成功就会调用 deferred 内部的dfd.resolve;失败调用dfd.reject

作用:将 promise 方法迁移到 dfd 对象上,通过对象直接访问能够少一层嵌套;

通过示例看一下延迟对象的效果:

  • 不使用延迟函数:需嵌套一层new Promise
function test() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(200)
    }, 1000);
  })
}
  • 使用延迟函数:无需嵌套new Promise
function test() {
  let dfd = Promise.deferred();
  setTimeout(() => {
    dfd.resolve(200);
  }, 1000);
  return dfd.promise;
}

结论:

  • 对比两种方式代码,使用延迟函数明显少了一层 new Promise 的嵌套;
  • 每次调用Promise.deferred()就能够立即得到一个全新的 promise 实例;
  • 延迟对象,就是对象 deferred 中包含了 Promise 的“延迟”处理;

3,执行测试

promises-aplus-tests  xxx(promise入口文件路径)

image.png

通过了 Promise A+ 规范 872 个测试用例;


五,结尾

本篇,主要对 Promise 源码进行完善并通过 promise-aplus-tests 测试,主要涉及以下几个点:

  • 完善 Promise 源码:支持两种嵌套 promise 的情况;
  • 分析 Promise 的执行过程;
  • 创建延迟对象并通过 promise-aplus-tests 测试;

下篇,继续实现 Promise.resolve 和 Promise.reject;


维护日志

  • 20211102
    • 添加了“延迟函数部分”的说明和示例;
    • 添加了对两种“嵌套返回 promise”的支持;
    • 优化了Promise 执行过程分析的描述;
    • 重新调整了文章一二级目录、结尾、摘要;
  • 20211115
    • 修改错别字;

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

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

相关文章

BUUCTF-Reverse Writeup【持续更新】

本文示例程序可见 BUUCTF 官网或者 github easyre | 入门级 方法一:WinHex 打开 easyre.exe,浏览一下字符串,发现有flag方法二:IDA Pro 打开 easyre.exe,能直接看到flag,或者 F5 反汇编看到逻辑是输入两个…

怎么把多个JPG合并成一个PDF?还不快来学

我们通常在处理工作文件时会有很多JPG图片需要传输,不过JPG图片数量一般都非常多,我们需要一张一张的进行传输,不仅会浪费很多时间,还很不方便查看,所以我们就可以及将JPG图片合并到一个PDF文件中,这样就可…

直流电机控制器设计

导读:本文主要介绍了DC-motor电流环和速度环的PI控制器参数的设计,并且简单介绍了设计控制器所需要的背景知识,相关仿真文件的下载地址在文章末尾可供大家下载。 DC-motor 的数学模型 直流电机电枢绕组的电压方程为:

阿里紧急辟谣,全球总部始终在杭州,造谣者请放过民营经济吧

树欲静,而风不止。春节期间又一则关于阿里的谣言,再次广泛传播。谣言称,“阿里在新加坡筹建全球总部”。这则谣言前后,网络上还有许多关于阿里早期创始人移民的各种传言。2023年1月27日下午,阿里集团相关负责人进行了辟…

SparkSQL中4个排序的区别

常用的四个排序BY ORDER BYSORT BYDISTRIBUTE BYCLUSTER BY 此文只是结合官方文档和案例做一下说明下功能 数据集描述 数据如上所示,并手动将分区数设定在2个,默认以 id 字段作为分区依据 原始数据分布 情况如下 相同id的数据都分到了同一个分区内 ORDER BY子句 官网说明: …

JavaScript 执行上下文

什么是执行上下文 JavaScript是一种客户端脚本语言,通常在Web浏览器中执行。当您在浏览器中加载网页时,浏览器会解析HTML文档并创建文档对象模型 (DOM)。在这个过程中,浏览器会寻找包含JavaScript代码的script标签并执行这些代码。 当浏览器…

46.Isaac教程--在机器人应用Deepstream

在机器人应用Deepstream ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录在机器人应用Deepstream技术组件工具技术 NVIDIA DeepStream SDK 为基于 AI 的视频和图像感知以及多传感器处理提供了完整的流分析工具包。 DeepStream 是 NVIDIA…

NUXT学习笔记【part1】nuxt的安装、自定义布局、路由跳转

一、NUXT概述 NUXT 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。NUXT使用服务端渲染技术,可以获得更快的内容到达时间&a…

markdown 写微信公众号,排版交给 mdnice

mdnice 墨滴软件( https://product.mdnice.com/ ),提供了以面向微信公众号内容排版为主的辅助工具 Markdown Nice[1],效果堪称完美,极大的提升了发布微信公众号文章的效率。 使用其在线编辑器( https://e…

L4 Latent Variable Model

Lecture4 Latent Variable Model 在之前我们所介绍的Autoregressive Model和Flow Model中,讨论的都是observable的数据,及一切数据都是可以观测到的。这一讲主要讨论的是latent variable model,即有些变量我们是无法直接观测的。 Latent Va…

2022尚硅谷SSM框架跟学(六)Spring MVC基础一

2022尚硅谷SSM框架跟学 六Spring MVC基础一三、SpringMVC1.SpringMVC简介1.1什么是MVC1.2什么是SpringMVC1.3SpringMVC的特点2入门案例2.1开发环境2.2创建maven工程(1)添加web模块(2)打包方式:war(3)引入依赖2.3配置web.xml(1)默认配置方式(2)扩展配置方式2.4创建请…

C++封装Halcon算法动态链接库Dll补充

前面写了一篇关于C封装DLL的文章,这里是做一个补充。 一、关于如何配置halcon和opencv库不再多说,前面文章介绍的很详细。下面介绍封装新增的东西。 1.1 首先创建类function1,并编写function.h和function1.cpp代码。 function1.h代码 #pra…

处理任务失败附实验(RH294)

首先,一个任务执行失败是肯定会遇见的事情而这时候,就需要一个备用的计划忽略任务失败默认情况下 任务失败的时候play会终止但是,我们可以通过忽略失败的任务来覆盖此行文使用关键字 ignore_errors举个栗子- name: Latest version of notapkg…

《流浪地球 2》 Deepfake 小试牛刀,45+ 吴京「被」年轻,变身 21 岁小鲜肉

内容一览:在春节档科幻电影「流浪地球 2」中,主演吴京、刘德华、沙溢等人饰演的角色,跨越 14 年之久,视效团队正是借助 Deepfake de-aging 技术,呈现出了演员不同年龄段的容貌。 关键词:De-aging Deepfa…

回收租赁商城系统功能拆解13讲-分销会员

回收租赁系统适用于物品回收、物品租赁、二手买卖交易等三大场景。 可以快速帮助企业搭建类似闲鱼回收/爱回收/爱租机/人人租等回收租赁商城。 回收租赁系统支持智能评估回收价格,后台调整最终回收价,用户同意回收后系统即刻放款,用户微信零…

springboot应用项目的打包和部署

jar包方式打包部署 springboot项目在为进行打包处理时就已经可以进行运行调试,是因为springboot项目内嵌了Tomcat服务器. .1添加Maven打包插件。在对Spring Boot项目进行打包(包括ar包和War包)前,需要在项目pom.xml文件中加入Maven打包插件&#xff0c…

倾角传感器的六大应用场合

倾角传感器又名水平传感器、水平仪、倾角仪,是角度传感器的一种,是运用惯性原理的一种加速度传感器,可以通过检测使用环境中的倾斜角度判断设备的倾斜状态,并在监测物出现角度偏差时,倾角传感器能够将异常数据传达给管…

【自学Docker 】Docker export命令

Docker export命令 概述 docker export教程 docker export 命令可以用于将 Docker容器 里的文件系统作为一个 tar 归档文件导出到标准输出。docker export 命令后面的 CONTAINER 可以是容器Id,或者是容器名。 docker save 命令用于将 Docker 镜像保存成 tar 包&a…

5nm三苯甲基/P3HT-PY/CdSe油溶性碳量子点CdSe/ZnS-PrPC的制备方法

5nm三苯甲基/P3HT-PY/CdSe油溶性碳量子点CdSe/ZnS-PrPC的制备方法今天小编分享制备三苯甲基功能化油溶性碳量子点的制备过程,一起看看吧:三苯甲基功能化油溶性碳量子点的制备过程:将50 mg Amino-CQDs放置在100 mL圆底烧瓶中,加入 50 mL二氯甲…

PyQt6-QLabel学习笔记

QLabel支持文本或图片显示,是任何GUI设计中最常用的控件之一。一、QLabel基本概述QLabel标签用于显示,可以以多种方式进行外观配置,没有提供用户交互功能,但是可以用于为另一个小部件指定焦点助记符或快捷键。显示内容类型类型说明…