一,前言

上一篇,实现 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 就是 valueresolve(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 有可能是一个 promisethis.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 = valuethis.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,进入成功回调处理,返回Promisereturn 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+规范内容:

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;// 别人实现的 Promisepromise = 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入口文件路径)

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


五,结尾

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

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

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


维护日志

  • 20211102

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

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

  1. Node进阶——之事无巨细手写Koa源码

    作者 rocYoung Koa是一个基于Node.js的Web开发框架,特点是小而精,对比大而全的Express(编者按:此处是相对来说,国内当然是有Egg.js和ThinkJS),两者虽然由同一团队 ...

  2. Node进阶—事无巨细手写Koa源码

    作者 rocYoung Koa是一个基于Node.js的Web开发框架,特点是小而精,对比大而全的Express(编者按:此处是相对来说,国内当然是有Egg.js和ThinkJS),两者虽然由同一团队 ...

  3. node进阶——之事无巨细手写koa源码(转)

    https://juejin.im/post/5ba48fc4e51d450e704277fa koa是一个基于nodejs的web开发框架,特点是小而精,对比大而全的express,两者虽然由同一团 ...

  4. java 手写签名,signature java html5+ 手写签名 源码 Develop 238万源代码下载- www.pudn.com...

    文件名称: signature下载 收藏√  [ 5  4  3  2  1 ] 开发工具: Java 文件大小: 491 KB 上传时间: 2013-08-03 下载次数: 17 提 供 者: 孙晨 ...

  5. 【手写 Vuex 源码】第五篇 - Vuex 中 Mutations 和 Actions 的实现

    一,前言 上一篇,主要介绍了 Vuex 中 getters 的实现,主要涉及以下几个点: 将选项中的 getters 方法,保存到 store 实例中的 getters 对象中: 借助 Vue 原生 ...

  6. 跨年巨作 13万字 腾讯高工手写JDK源码笔记 带你飙向实战

    灵魂一问,我们为什么要学习JDK源码? 当然不是为了装,毕竟谁没事找事虐自己 ... 1.面试跑不掉.现在只要面试Java相关的岗位,肯定或多或少会会涉及JDK源码相关的问题. 2.弄懂原理才不慌.我 ...

  7. 【Java进阶营】膜拜 13万字 腾讯高工手写JDK源码笔记带你飙向实战

    灵魂一问,我们为什么要学习JDK源码? 当然不是为了装,毕竟谁没事找事虐自己 - 1.面试跑不掉.现在只要面试Java相关的岗位,肯定或多或少会会涉及JDK源码相关的问题. 2.弄懂原理才不慌.我们作 ...

  8. 手写Mybatis源码(原来真的很简单!!!)

    目录 一.JDBC操作数据库_问题分析 二.自定义持久层框架_思路分析 三.自定义框架_编码 1.加载配置文件 2.创建两个配置类对象 3.解析配置文件,填充配置类对象 4.创建SqlSessionF ...

  9. react学习笔记 react-router-dom react-redux基础使用及手写基础源码 组件反射 react原理

    vdom diff 高效的diff算法 新老vdom树比较 更新只需要更新节点 数据变化检测 batch dom读写 组件多重继承 //parent components export default ...

  10. 安卓手写字迹源码(毛笔,喷枪,马克笔等效果)

    之前项目需要,要在手机上实现笔迹的效果,这类的应用多了,但是源码在全球最大局域网内却找不到~~ 一年前是想着自己做的,当时知道安卓设备的touch是可以获取伪压力感应值 即手指压力越大,皮肤接触面积也 ...

最新文章

  1. 【C++】Google C++编码规范(四):其他C++
  2. Redis+keepalived 主从搭建
  3. php如何监听页面滚动,html5中在元素滚动条在滚动时触发的事件onscroll
  4. java swarm_科学网—Java_Swarm编程:遇到麻烦了...... - 高德华的博文
  5. HBase数据模型和读写原理
  6. Java中大数据数组,Java基础学习笔记之数组详解
  7. Eclipse\myeclipse加载项目building workspace过久
  8. 程序员的 升级 ,价值观的改变
  9. 论文解读:Prediction of Protein–Protein Interaction Sites Using Convolutional Neural Network
  10. remoting 最简单的一个例子
  11. adb 详细使用文档(ADB命令使用大全)
  12. MessageBox中涉及到的宏定义
  13. Android内核层驱动程序UAF漏洞提权实例
  14. 3d智慧城市线上3d模型展示可视化平台
  15. 群论:群的定义与阿贝尔群
  16. 点任务栏不切换窗口_如何使您的任务栏按钮始终切换到最后一个活动窗口
  17. Oracle 查看和修改数据库时区
  18. 王垠:对博士学位说永别
  19. wgt文件怎么打开|wgt文件怎么查看内部文件|apk怎么查看内部文件
  20. 精神心理科医生:抑郁症正在好转的5种表现

热门文章

  1. 阿里云(香港节点瘫痪)复盘
  2. multisim安装后无法连接数据库_计算机重装系统时遭遇错误意外重启后无法安装,这是什么原因?...
  3. 阿里云服务器通用型实例g5 怎么样好不好?性能配置分析
  4. C语言中,再对文件的操作模式中,a和a+、w和w+、r和r+有什么区别?
  5. 解决无法启动ROS节点“ERROR:cannot launch node of type [map_server/map_server]:map_server”的问题
  6. 密集匹配实验数据共享
  7. String的比较,“==”比较,equals()比较
  8. gstreamer插件开发指南(一)
  9. php 中遍历数组时使用引用出现的问题
  10. functools 可调用对象上的高阶函数和操作