手写Promise方法(直击Promise A+规范)

news2024/11/29 16:38:30

前言:大家好,我是前端獭子。高效优雅是我的创作方式。学习是一个渐进的过程,要把知识串联起来才能解决某一方面的问题。


Promise 构造函数

我们先来写 Promise 构造函数的属性和值,以及处理new Promise()时会传入的两个回调函数。如下:

class myPromise {constructor(func) {this.state = 'pending' // Promise状态this.value = undefined // 成功的值this.reason = undefined // 错误的值this.resolveCallbacks = [] // 收集解决回调函数this.rejectCallbacks = [] // 收集错误回调函数try { // 对传入的函数进行try...catch...做容错处理func(this.resolve, this.reject) // 执行传入的两个回调函数} catch (e) {this.reject(e)}}
} 

三个状态(pending、rejected和fulfilled)

pending:待定状态。待定 Promise 。只有在then方法执行后才会保持此状态。

rejected:拒绝状态。终止 Promise 。只有在reject方法执行后才会由 pending 更改为此状态。

fulfilled:解决状态。终止 Promise 。只有在resolve方法执行后才会由 pending 更改为此状态。

注意:其中只有 pedding 状态可以变更为 rejected 或 fulfilled 。rejected 或 fulfilled 不能更改其他任何状态。


三个方法(resolve、reject和then)

resolve方法实现要点

1.状态由pendingfulfilled。
2.resolve方法传入的value参数赋值给this.value
3.按顺序执行resolveCallbacks里面所有解决回调函数
4.利用call方法将解决回调函数内部的 this 绑定为undefined

坑点 1resolve方法内部 this 指向会丢失,进而造成this.value丢失。

解决办法:我们将resolve方法定义为箭头函数。在构造函数执行后,箭头函数可以绑定实例对象的 this 指向。

// 2.1. Promise 状态
resolve = (value) => { // 在执行构造函数时内部的this通过箭头函数绑定实例对象if (this.state === 'pending') {this.state = 'fulfilled' // 第一点this.value = value // 第二点while (this.resolveCallbacks.length > 0) { // 第三点this.resolveCallbacks.shift().call(undefined) // 第四点}}
} 

reject方法实现要点

1.状态由pendingrejected
2.reject方法传入的reason参数赋值给this.reason
3.按顺序执行rejectCallbacks里面所有拒绝回调函数
4.利用call方法将拒绝回调函数内部的 this 绑定为undefined

坑点 1reject 方法内部 this 指向会丢失,进而造成this.reason丢失。

解决办法:我们将reject方法定义为箭头函数。在构造函数执行后,箭头函数可以绑定实例对象的 this 指向。

// 2.1. Promise 状态
reject = (reason) => { // 在执行构造函数时内部的this通过箭头函数绑定实例对象if (this.state === 'pending') {this.state = 'rejected' // 第一点this.reason = reason // 第二点while (this.rejectCallbacks.length > 0) {// 第三点this.rejectCallbacks.shift().call(undefined) // 第四点}}
} 

then方法实现要点

1.判断then方法的两个参数onRejectedonFulfilled是否为function。1.1 onRejectedonFulfilled都是function,继续执行下一步。1.2 onRejected不是function,将onRejected赋值为箭头函数,参数为reason执行throw reason1.3 onFulfilled不是function,将onFulfilled赋值为箭头函数,参数为value执行return value2.当前Promise状态为rejected:2.1 onRejected方法传入this.reason参数,异步执行。2.2 对执行的onRejected方法做容错处理,catch错误作为reject方法参数执行。3.当前Promise状态为fulfilled:3.1 onFulfilled方法传入this.value参数,异步执行。3.2 对执行的onFulfilled方法做容错处理,catch错误作为reject方法参数执行。4.当前Promise状态为pending:4.1 收集onFulfilledonRejected两个回调函数分别pushresolveCallbacksrejectCallbacks。4.2 收集的回调函数同样如上所述,先做异步执行再做容错处理。5.返回一个 Promise 实例对象。```
// 2.2. then 方法
then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : value => value // 第一点 onRejected = typeof onRejected === ‘function’ ? onRejected : reason => { throw reason } // 第一点 const p2 = new myPromise((resolve, reject) => {if (this.state === ‘rejected’) { // 第二点queueMicrotask(() => {try {onRejected(this.reason)} catch (e) {reject(e)}})} else if (this.state === ‘fulfilled’) { // 第三点queueMicrotask(() => {try {onFulfilled(this.value)} catch (e) {reject(e)}})} else if (this.state === ‘pending’) { // 第四点this.resolveCallbacks.push(() => {queueMicrotask(() => {try {onFulfilled(this.value)} catch (e) {reject(e)}})})this.rejectCallbacks.push(() => {queueMicrotask(() => {try {onRejected(this.reason)} catch (e) {reject(e)}})})}})return p2 // 第五点
}


* * *

Promise 解决程序(resolvePromise方法)
------------------------------

旁白:其实这个解决程序才是实现核心Promise最难的一部分。因为Promise A+规范对于这部分说的比较绕。

我们直击其实现要点,能跑通所有官方用例就行。如下:

1.如果x和promise引用同一个对象:1.1 调用`reject`方法,其参数为`new TypeError()`2.如果x是一个promise或x是一个对象或函数:2.1 定义一个`called`变量用于记录`then.call`参数中两个回调函数的调用情况。2.2 定义一个`then`变量等于`x.then`2.3 `then`是一个函数。使用`call`方法绑定`x`对象,传入**解决回调函数**和**拒绝回调函数**作为参数。同时利用`called`变量记录`then.call`参数中两个回调函数的调用情况。2.4 `then`不是函数。调用`resolve`方法解决Promise,其参数为`x`2.5 对以上 **2.2** 检索属性和 **2.3** 调用方法的操作放在一起做容错处理。`catch`错误作为`reject`方法参数执行。同样利用`called`变量记录`then.call`参数中两个回调函数的调用情况。3.如果x都没有出现以上两种状况:调用`resolve`方法解决Promise,其参数为`x````
// 2.3 Promise解决程序
function resolvePromise(p2, x, resolve, reject) {if (x === p2) {// 2.3.1 如果promise和x引用同一个对象reject(new TypeError())} else if ((x !== null && typeof x === 'object') || typeof x === 'function') {// 2.3.2 如果x是一个promise// 2.3.3 如果x是一个对象或函数let calledtry {let then = x.then // 检索x.then属性,做容错处理if (typeof then === 'function') {then.call(x, // 使用call绑定会立即执行then方法,做容错处理(y) => { // y也可能是一个Promise,递归调用直到y被resolve或rejectif (called) { return }called = trueresolvePromise(p2, y, resolve, reject)},(r) => {if (called) { return }called = truereject(r)})} else {resolve(x)}} catch (e) {if (called) { return }called = truereject(e)}} else {resolve(x)}
} 

called变量的作用:记录then.call传入参数(两个回调函数)的调用情况

根据Promise A+ 2.3.3.3.3规范:两个参数作为函数第一次调用优先,以后的调用都会被忽略。

因此我们在以上两个回调函数中这样处理:

1.已经调用过一次:此时called已经为true,直接return忽略
2.首次调用:此时calledundefined,调用后called设为true

注意:2.3 中的catch可能会发生(两个回调函数)已经调用但出现错误的情况,因此同样按上述说明处理。


运行官方测试用例

在完成上面的代码后,我们最终整合如下:

class myPromise {constructor(func) {this.state = 'pending'this.value = undefinedthis.reason = undefinedthis.resolveCallbacks = []this.rejectCallbacks = []try {func(this.resolve, this.reject)} catch (e) {this.reject(e)}}resolve = (value) => {if (this.state === 'pending') {this.state = 'fulfilled'this.value = valuewhile (this.resolveCallbacks.length > 0) {this.resolveCallbacks.shift().call(undefined)}}}reject = (reason) => {if (this.state === 'pending') {this.state = 'rejected'this.reason = reasonwhile (this.rejectCallbacks.length > 0) {this.rejectCallbacks.shift().call(undefined)}}}then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => valueonRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }const p2 = new myPromise((resolve, reject) => {if (this.state === 'rejected') {queueMicrotask(() => {try {const x = onRejected(this.reason)resolvePromise(p2, x, resolve, reject)} catch (e) {reject(e)}})} else if (this.state === 'fulfilled') {queueMicrotask(() => {try {const x = onFulfilled(this.value)resolvePromise(p2, x, resolve, reject)} catch (e) {reject(e)}})} else if (this.state === 'pending') {this.resolveCallbacks.push(() => {queueMicrotask(() => {try {const x = onFulfilled(this.value)resolvePromise(p2, x, resolve, reject)} catch (e) {reject(e)}})})this.rejectCallbacks.push(() => {queueMicrotask(() => {try {const x = onRejected(this.reason)resolvePromise(p2, x, resolve, reject)} catch (e) {reject(e)}})})}})return p2}
}
function resolvePromise(p2, x, resolve, reject) {if (x === p2) {reject(new TypeError())} else if ((x !== null && typeof x === 'object') || typeof x === 'function') {let calledtry {let then = x.thenif (typeof then === 'function') {then.call(x,(y) => {if (called) { return }called = trueresolvePromise(p2, y, resolve, reject)},(r) => {if (called) { return }called = truereject(r)})} else {resolve(x)}} catch (e) {if (called) { return }called = truereject(e)}} else {resolve(x)}
}
// 新加入部分
myPromise.deferred = function () {let result = {};result.promise = new myPromise((resolve, reject) => {result.resolve = resolve;result.reject = reject;});return result;
}
module.exports = myPromise; 

新建一个文件夹,放入我们的 myPromise.js 并在终端执行以下命令:

npm init -y
npm install promises-aplus-tests 

package.json 文件修改如下:

{"name": "promise","version": "1.0.0","description": "","main": "myPromise.js","scripts": {"test": "promises-aplus-tests myPromise"},"keywords": [],"author": "","license": "ISC","devDependencies": {"promises-aplus-tests": "^2.1.2"}
} 

开始测试我们的手写 Promise,在终端执行以下命令即可:

npm test 

Promise 其他方法补充

容错处理方法

Promise.prototype.catch()

catch(onRejected) {return this.then(undefined, onRejected)
} 

Promise.prototype.finally()

finally(callback) {return this.then(value => {return myPromise.resolve(callback()).then(() => value)},reason => {return myPromise.resolve(callback()).then(() => { throw reason })})
} 

静态方法

Promise.resolve()

static resolve(value) {if (value instanceof myPromise) {return value// 传入的参数为Promise实例对象,直接返回} else {return new myPromise((resolve, reject) => {resolve(value)})}
} 

Promise.reject()

static reject(reason) {return new myPromise((resolve, reject) => {reject(reason)})
} 

Promise.all()

static all(promises) {return new myPromise((resolve, reject) => {let countPromise = 0 // 记录传入参数是否为Promise的次数let countResolve = 0 // 记录数组中每个Promise被解决次数let result = [] // 存储每个Promise的解决或拒绝的值if (promises.length === 0) { // 传入的参数是一个空的可迭代对象resolve(promises)}promises.forEach((element, index) => {if (element instanceof myPromise === false) { // 传入的参数不包含任何 promise++countPromiseif (countPromise === promises.length) {queueMicrotask(() => {resolve(promises)})}} else {element.then(value => {++countResolveresult[index] = valueif (countResolve === promises.length) {resolve(result)}},reason => {reject(reason)})}})})
} 

Promise.race()

static race(promises) {return new myPromise((resolve, reject) => {if (promises.length !== 0) {promises.forEach(element => {if (element instanceof myPromise === true)element.then(value => {resolve(value)},reason => {reject(reason)})})}})
} 

上述所有实现代码已放置我的Github仓库。可自行下载测试,做更多优化。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

【Git】IDEA集合Git和码云

目录 7、IDEA集合Git 7.1 配置Git忽略文件-IDEA特定文件 7.2 定位 Git 程序 7.3 初始化本地库 7.4 添加到暂存区 7.5 提交到本地库 7.6 切换版本 7.7 创建分支 7.8 切换分支 7.9 合并分支 7.10 解决冲突 8、 Idea集成码云 8.1 IDEA 安装码云插件 8.2 分析工程到码…

QHashIterator-官翻

QHashIterator Class template <typename Key, typename T> class QHashIterator QHashIterator 类为 QHash 和 QMultiHash 提供 Java 风格的常量迭代器。更多内容… 头文件:#include qmake:QT core 所有成员列表&#xff0c;包括继承的成员废弃的成员 公共成员函数…

硫酸锂溶液除钙镁树脂系统

H-93锂盐净化除钙镁镁螯合树脂是包含氨甲膦酸基连接到聚苯乙烯共聚物的一种耐用的大孔树脂。 CH-93是用于锂盐净化除钙镁从含有一价阳离子的废水处理中选择性的除去二价金属阳离子。使二价金属阳离子以及由其他二价阳离子可以像钙一样容易地从一价阳离子中分离出来。 CH-93是…

[论文阅读笔记19]SiamMOT: Siamese Multi-Object Tracking

这是CVPR2021的一篇文章, 是利用SOT的一些思想来进行MOT的运动估计. 文章地址: 文章 代码地址: 代码 0. 摘要 本文提出了一个孪生(Siamese)式的MOT网络, 该网络用来估计帧间目标的运动. 为了探究运动估计对多目标跟踪的影响, 本文提出了两种运动建模方式: 显式和隐式. 本文在…

C++(41)-低版本升级到VS2019项目时遇到的问题(2)

1.错误码&#xff1a;MSB8066 代码为3 QT 项目老版本升级到新版本造成的&#xff0c; 1.重新加载项目&#xff1a; 扩展->QT VS tools->Open QT project files-> 2.添加QT模块&#xff1a;QT Project-Settings -> QT Modules2.无法打开QT的头文件 3.…

电脑自带的录屏放在哪里了?轻松弄懂,看这篇文章就明白了

有很多小伙伴有这个疑问&#xff0c;电脑自带的录屏放在哪里了&#xff1f;其实&#xff0c;电脑自带的录屏工具并不是所有电脑都要&#xff0c;具体要看你的电脑品牌和操作系统&#xff0c;Windows系统和Mac系统的电脑都自带了录屏工具&#xff0c;下面跟着小编一起来看看吧。…

Redis:缓存一致性问题(缓存更新策略)

Redis缓存的一致性1. 缓存1.1 缓存的作用&#xff1a;1.2 缓存的成本&#xff1a;2. 缓存模型3. 缓存一致性问题3.1 引入3.2 解决(1) 先更新数据库&#xff0c;再手动删除缓存(2) 使用事务保证原子性(3) 以Redis中的TTL为兜底3.3 案例&#xff1a;商铺信息查询和更新(1) 查询商…

“双碳”目标下二氧化碳地质封存技术应用前景及模型构建实践方法与讨论

我国二氧化碳地质封存技术起步较晚&#xff0c;目前仍没有一套相对完整的行业规范&#xff1b;且就该技术而言&#xff0c;涉及环节众多&#xff0c;理论相对复杂&#xff0c;对于行业的新入局者不太友好。因此&#xff0c;结合时代背景&#xff0c;我们首次尝试对二氧化碳地质…

【面试1v1实景模拟】Spring事务 一文到底

老面👴:小伙子,了解Spring的事务吗? 解读🔔:这个必须了解,不了解直接挂~😂😂😂,但面试官肯定不是想听你了解两个字,他是想让你简单的介绍下。 笑小枫🍁:了解,事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,比如说 MySQL。为…

C++——C++11第三篇

目录 包装器 function包装器 bind 包装器 function包装器 function包装器 也叫作适配器。C中的function本质是一个类模板&#xff0c;也是一个包装器。 上面的程序验证&#xff0c;我们会发现useF函数模板实例化了三份。 包装器可以很好的解决上面的问题 &#xff0c;让它只实…

Android新启动模式之singleInstancePerTask

Android新启动模式之singleInstancePerTask 一.singleInstancePerTask介绍 singleInstancePerTask为android12新增的在standard、singleTop、singleTask、singleInstance之后的第五种启动模式。 Android12对于singleInstancePerTask描述如下(sdk中在platforms/android-31/d…

05- 形态学及图像的开闭运算 (OpenCV基础) (机器视觉)

知识重点 二值化操作 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)&#xff0c;对灰度图像操作, 全局阈值&#xff0c;整幅图像采用同一个数作为阈值 。 自适应阈值二值化 dst cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, …

做测试5年,靠业务熟悉吃老本,技术短板暴露,30岁被无情辞退...

朋友跟我诉苦&#xff0c;最近他被公司无情辞退了。测试几年&#xff0c;月薪10k&#xff0c;如今已经30了&#xff0c;接下来不知道该怎么办&#xff0c;让我帮他想想办法... 几年下来&#xff0c;也算是公司的骨干成员&#xff0c;不说有功&#xff0c;但一定无过。公司业务…

乐山持点科技:抖音38好物节活动运营要求

抖音38好物节活动马上就要到了&#xff0c;很多人都还不知道抖音38好物节活动的运营要求&#xff0c;乐山持点科技小编这就来给各位抖音达人们分享。5.1 商家活动管控规范5.1.1 以上条件为满足参加活动的基本条件&#xff0c;最终是否能够参加活动&#xff0c;以平台的最终审核…

kubernetes入门介绍,从0到1搭建并使用

Kubernetes是一个容器编排系统&#xff0c;用于自动化应用程序部署、扩展和管理。本指南将介绍Kubernetes的基础知识&#xff0c;包括基本概念、安装部署和基础用法。 基础介绍 Kubernetes是Google开发的开源项目&#xff0c;是一个容器编排系统&#xff0c;可以自动化部署、…

【100个 Unity实用技能】☀️ | C#泛型集合常用方法,查找符合要求的第一个元素并返回

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…

学数据结构第一个是学链表?不,是它

大家好&#xff0c;我是五月。前言以前很多小白都来询问过关于数据结构的内容&#xff0c;问题基本都是想学链表&#xff0c;堆栈、队列、树这些该怎么下手。一方面我表示赞许&#xff0c;另一方面又觉得他们对数据结构这个东西真是知之甚少。我告诉他们&#xff0c;第一个要学…

常用Linux的ssh远程终端连接工具

putty 说明 putty是最简单的SSH工具&#xff0c;无需安装&#xff0c;支持多系统版本&#xff0c;下载后就可以直接使用。 优点&#xff1a; 1.免费 2.免安装 缺点&#xff1a; 1、不支持标签模式&#xff1b; 2、默认设置不友好&#xff0c;很多功能都需要额外配置才行&…

Vue Slot (四种方式:超详细)

slot(插槽)的概念是把外层的内容塞进子元件的指定位置里。跟插槽的字面意思一样,前提是:有插口才能插。子元件需要开一个插口(slot),才可以在外层元件把内容塞进子元件里。 slot(插槽) 可分为四种: slot(插槽)Named slot(具名插槽)Scoped slot(作用域插槽)Dyn…

掌握MySQL分库分表(七)广播表、绑定表实战,水平分库+分表实现及之后的查询和删除操作

文章目录什么是广播表广播表实战数据库配置表Java配置实体类配置文件测试广播表水平分库分表配置文件运行测试什么是绑定表&#xff1f;绑定表实战配置数据库配置Java实体类配置文件运行测试水平分库分表后的查询和删除操作查询操作什么是广播表 指所有的分片数据源中都存在的…