js手写Promise(上)

目录

  • 构造函数
    • resolve与reject
    • 状态改变
      • 状态改变后就无法再次改变
    • 代码优化
    • 回调函数中抛出错误
  • then
    • onFulfilled和onRejected的调用时机
    • 异步then
    • 多个then

如果是不知道或者对Promise不熟悉的铁铁可以先看我这篇文章
Promise

构造函数

在最开始,我们先不去考虑Promise内部是怎么实现,而是先将自己的Promise声明出来,这里我使用ES6class来声明

class MyPromise {}

在我们new一个Promise的时候会传入一个回调函数,这个回调函数有两个形参,一个resolve,一个reject,这个函数将交给Promise立即执行,所以我们的constructor可以这么写

class MyPromise {constructor(func) {func(resolve, reject)}
}

值得注意的是,Promise本身就是一个任务,而回调函数表示的是任务的执行过程,所以constructor中的形参应该叫executor而不是func

class MyPromise {constructor(executor) {executor(resolve, reject)}
}

resolve与reject

resolvereject也是函数,那么这两个函数定义在哪呢,有2种方案

  1. 定义在constructor

    constructor(executor) {const resolve = (data) => {}const reject = (reason) => {}executor(resolve, reject)
    }
    
  2. 将其变为原型方法

    class MyPromise {
    constructor(executor) {func(this.#resolve, this.#reject)
    }
    #reject(reason) { }
    #resolve(data) { }
    }
    

    因为这个函数我们只会在类的内部使用,并不希望用户能在外部访问,所以我们将它定义为私有成员
    只不过这么写的话会有this的指向问题,我们需要使用强制绑定来将函数绑定到正确的地方

    class MyPromise {
    constructor(executor) {func(this.#resolve.call(this), this.#reject.call(this))
    }
    #reject(reason) { }
    #resolve(data) { }
    }
    

这里我选择第一种方法

状态改变

现在我们声明了resolvereject两个函数,但具体这两个函数做什么我们并不清楚,事实上这两个函数做的都是同一件事,改变当前Promise实例的状态与值,只不过resolve是将当前实例的状态改为fulfilled,而reject是将当前实例的状态改为rejected,明白了这一点我们就能写出如下代码

class MyPromise {#state = "pending"#value = nullconstructor(executor) {const resolve = (data) => {this.#state = "fulfilled"this.#value = data}const reject = (reason) => {this.#state = "rejected"this.#value = reason}executor(resolve, reject)}
}

我们声明了两个私有属性,无论是state还是value我们都不希望用户能从外部访问,state用于记录当前实例的状态,而value用于记录当前实例得到的

状态改变后就无法再次改变

这么写就完了吗?当然没有,在Promise中状态一旦确定就不能再更改,反映到代码层面就是无论是在回调函数中写多少个resolverejectPromise都只会执行第一个,而我们的Promise中目前并没有实现这个功能

const resolve = (data) => {if (this.#state !== "pending") returnthis.#state = "fulfilled"this.#value = data
}
const reject = (reason) => {if (this.#state !== "pending") returnthis.#state = "rejected"this.#value = reason
}

我们在resolvereject上都加了一行判断,如果当前实例的state不是pending的话就说明状态已经改变,不能再继续执行
写到这里我们发现resolvereject函数中的重复代码有点多,所以我们可以将其封装成一个独立的函数

class MyPromise {#state = "pending"#value = nullconstructor(executor) {const resolve = (data) => {this.#changeState("fulfilled", data)}const reject = (reason) => {this.#changeState("rejected", reason)}executor(resolve, reject)}#changeState(state, value) {if (this.#state !== "pending") returnthis.#state = statethis.#value = value}
}

代码优化

现在我们发现在我们的代码中还存在着一些硬编码的部分,如状态不应该直接使用字符串而是需要使用变量存起来,这样如果以后状态的名称发生改变,我们也就只需要更改变量的内容

class MyPromise {#state = "pending"#value = nullstatic #PENDING = "pending"static #FULFILLED = "fulfilled"static #REJECTED = "rejected"constructor(executor) {const resolve = (data) => {this.#changeState(MyPromise.#FULFILLED, data)}const reject = (reason) => {this.#changeState(MyPromise.#REJECTED, reason)}executor(resolve, reject)}#changeState(state, value) {if (this.#state !== MyPromise.#PENDING) returnthis.#state = statethis.#value = value}
}

我们将三种状态用变量存起来,因为三个状态只会在内部使用而且每个实例都会拥有这三个状态,所以我将其定义为静态私有成员

回调函数中抛出错误

现在大部分问题我们都解决了,但是在回调函数中抛出错误的情况我们并没有处理,在Promise中如果回调函数中抛出了错误会被Promise内部捕获到,直接reject,那么我们的代码就可以这么写

constructor(executor) {const resolve = (data) => {this.#changeState(MyPromise.#FULFILLED, data)}const reject = (reason) => {this.#changeState(MyPromise.#REJECTED, reason)}try {executor(resolve, reject)} catch (error) {reject(error)}
}

至此我们就将MyPromise构造器部分完成了

then

PromiseA+规范中通篇都在说什么是Promise,简单地说就是Promise可以是一个对象或者是函数,但无论是什么都必须要有then方法,如果有then方法那就是Promise
所以then方法是Promise中的核心,同时也是手写Promise中最难的一部分,如果能将then方法手写出来那整个Promise就可以算是大部分完成了
我们回忆一下Promise中的then方法,发现then方法会传入两个参数,一个是成功时的回调函数,一个是失败时的回调函数,那我们可以这么定义

class MyPromise {then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {})}
}

因为then方法是每个实例都拥有并且用到的,所以我们将其定义为成员方法,为了实现Promise的链式调用所以then方法必须返回一个Promise,那么在这个返回的Promise中,我们究竟该做些什么呢

onFulfilled和onRejected的调用时机

onFulfilledonRejected什么时候调用,这个问题很好解决,依据当前Promise的状态判断是调用onFulfilled还是onRejected

then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {if (this.#state === MyPromise.#FULFILLED) onFulfilled(this.#value)if (this.#state === MyPromise.#REJECTED) onRejected(this.#value)})
}

这么写似乎并没有什么问题,那我们来测试一下

let p1 = new MyPromise((resolve, reject) => {resolve(123)
})
let p2 = new MyPromise((resolve, reject) => {setTimeout(() => {resolve(456)}, 1000)
})
p1.then(data => {console.log(data)
})
p2.then(data => {console.log(data)
})

结果
看得出来,p1成功运行了,但p2似乎有点问题,因为p2在运行到then的时候p2的状态还是pendingp2的状态会在一秒钟后才改变,但then方法早在这之前就调用了,所以为了避免这种情况,我们需要在状态改变的时候再次调用then方法

异步then

再次调用then方法说起来并不精确,我们其实真正想要的并不是调用then方法,而是想要在状态改变的时候调用onFulfiled或者onRejected,那么第一个问题就来了,我们在哪里能知道状态什么时候被改变了?答案是changeState
changeState是用来改变当前实例的状态的函数,当它第一次运行时状态肯定被改变,我们只需要在这里调用onFulfilled或者onRejected,但是有一个新问题,这两个回调函数都是直接传入then中的,我们无法在changeState中拿到这两个函数,那该怎么办呢?我们可以用一个中间变量存储

class MyPromise {#handler = {}#changeState(state, value) {if (this.#state !== MyPromise.#PENDING) returnthis.#state = statethis.#value = valueif (this.#state === MyPromise.#FULFILLED) this.#handler.onFulfilled(this.#value)else if (this.#state === MyPromise.#REJECTED) this.#handler.onRejected(this.#value)}then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {if (this.#state === MyPromise.#FULFILLED) onFulfilled(this.#value)else if (this.#state === MyPromise.#REJECTED) onRejected(this.#value)else this.#handler = {onFulfilled,onRejected,resolve,reject}})}
}

这样问题就解决了,但这里面的重复代码有点多,我们可以将其封装成一个函数

class MyPromise {#changeState(state, value) {if (this.#state !== MyPromise.#PENDING) returnthis.#state = statethis.#value = valuethis.#run()}then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {this.#handler = {onFulfilled,onRejected,resolve,reject}this.#run()})}#run() {if (this.#state === MyPromise.#FULFILLED) {this.#handler.onFulfilled(this.#value)}else if (this.#state === MyPromise.#REJECTED) {this.#handler.onRejected(this.#value)}}}

我们封装了一个run函数,这个函数专门用来执行then的回调,我们还是用上面那个代码测试
结果
至此异步then问题解决

多个then

有时我们会在一个实例上多次调用then方法,在实例的状态改变后这些then方法的回调函数应该继续执行,但我们的代码却并没有实现
多个then就意味着handler不是一个对象而是一个数组run方法也不再调用一个handler,而是遍历handlers,将对应状态的回调函数全都取出来执行

class MyPromise {#handlers = []then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {this.#handlersPush(onFulfilled, onRejected, resolve, reject)this.#run()})}#run() {if (this.#state === MyPromise.#PENDING) returnwhile (this.#handlers.length > 0) {const handler = this.#handlers.shift()if (this.#state === MyPromise.#FULFILLED) {handler.onFulfilled(this.#value)}else if (this.#state === MyPromise.#REJECTED) {handler.onRejected(this.#value)}}}#handlersPush(onFulfilled, onRejected, resolve, reject) {this.#handlers.push({onFulfilled,onRejected,resolve,reject})}
}

我们封装了一个辅助函数用于向handlers放入回调,在run中我们会一直在handlers里取出回调执行,我们使用以下代码测试

let p1 = new MyPromise((resolve, reject) => {resolve(123)
})
p1.then(data => {console.log("第一个then" + data)
})
p1.then(data => {console.log("第二个then" + data)
})

结果
至此,我们的Promise如下

class MyPromise {#state = "pending"#value = nullstatic #PENDING = "pending"static #FULFILLED = "fulfilled"static #REJECTED = "rejected"#handlers = []constructor(executor) {const resolve = (data) => {this.#changeState(MyPromise.#FULFILLED, data)}const reject = (reason) => {this.#changeState(MyPromise.#REJECTED, reason)}try {executor(resolve, reject)} catch (error) {reject(error)}}#changeState(state, value) {if (this.#state !== MyPromise.#PENDING) returnthis.#state = statethis.#value = valuethis.#run()}then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {this.#handlersPush(onFulfilled, onRejected, resolve, reject)this.#run()})}#run() {if (this.#state === MyPromise.#PENDING) returnwhile (this.#handlers.length > 0) {const handler = this.#handlers.shift()if (this.#state === MyPromise.#FULFILLED) {handler.onFulfilled(this.#value)}else if (this.#state === MyPromise.#REJECTED) {handler.onRejected(this.#value)}}}#handlersPush(onFulfilled, onRejected, resolve, reject) {this.#handlers.push({onFulfilled,onRejected,resolve,reject})}
}

因为内容过多,所以我将文章分为两篇,接下来的部分请看我的这篇文章
js手写Promise(下)

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

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

相关文章

Get Ready!这些 ALVA 应用即将上线 Vision Pro!

日前,苹果 Vision Pro 正式在美国上市,应用商店首批上线超过 600 款应用程序,出色的显示效果和交互体验,为更多应用提供了全新打开方式。 *图源:Apple 对此,作为全球领先的空间计算技术平台供应商&#xff…

基于蒙特卡洛的电力系统可靠性分析matlab仿真,对比EDNS和LOLP

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 1.课题概述 电力系统可靠性是指电力系统按可接受的质量标准和所需数量不间断地向电力用户供应电力和电能量的能力的量度,包括充裕度和安全性两个方面。发电系统可靠性是指统一并网的全部发电机…

猫头虎分享已解决Bug || SyntaxError: Unexpected token < in JSON at position 0

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …

FastDFS 分布式集群搭建详解

文章目录 前言1、整体架构2、安装配置FastDFS集群2.1 配置tracker2.2 配置storage 3、启动集群4、查看集群情况5、nginx配置5.1 配置storage的四台机器的nginx5.2 配置tracker的两台机器的nginx5.3 配置统一入口 前言 阅读本文章之前请先看上一篇单机版FastDFS安装配置详解&am…

寒假作业-day5

1>现有无序序列数组为23,24,12,5,33,5347&#xff0c;请使用以下排序实现编程 函数1:请使用冒泡排序实现升序排序 函数2:请使用简单选择排序实现升序排序 函数3:请使用直接插入排序实现升序排序 函数4:请使用插入排序实现升序排序 代码&#xff1a; #include<stdio.h&g…

基于SSM的餐厅点菜管理系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的餐厅点菜管理系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring …

使用 WMI 查询安全软件信息

在这篇文章中&#xff0c;我们将详细介绍如何使用 Windows Management Instrumentation (WMI) API 来查询当前计算机上安装的安全软件的基本信息。我们将分析代码的各个部分&#xff0c;并解释每个步骤所涉及的技术和原理。 一、什么是 WMI&#xff1f; WMI 是 Windows Manag…

常用的前端模块化标准总结

1、模块化标准出现以前使用的模块化方案&#xff1a; 1&#xff09;文件划分&#xff1a; 将不同的模块定义在不同的文件中&#xff0c;然后使用时通过script标签引入这些文件 缺点&#xff1a; 模块变量相当于是定义在全局的&#xff0c;容易造成变量名冲突&#xff08;即不…

[C#] 如何使用ScottPlot.WPF在WPF桌面程序中绘制图表

什么是ScottPlot.WPF&#xff1f; ScottPlot.WPF 是一个开源的数据可视化库&#xff0c;用于在 WPF 应用程序中创建高品质的绘图和图表。它是基于 ScottPlot 库的 WPF 版本&#xff0c;提供了简单易用的 API&#xff0c;使开发人员能够通过简单的代码创建各种类型的图表&#…

Web 目录爆破神器:Dirb 保姆级教程(附链接)

一、介绍 dirb 是一款用于目录爆破的开源工具&#xff0c;旨在帮助渗透测试人员和安全研究人员发现目标网站上的隐藏目录和文件。它使用字典文件中的单词来构建 URL 路径&#xff0c;然后发送 HTTP 请求来检查这些路径是否存在。 以下是 dirb 工具的一些特点和基本用法&#…

【从零开始学设计模式】第三章_工厂方法模式

第三章_工厂模式 1.介绍 1.1定义 定义一个创建对象的接口&#xff0c;让其子类自己决定实例化哪一个工厂类&#xff0c;工厂模式使其创建过程延迟到子类进行。 1.2解决的问题 创建者和调用者的耦合&#xff0c;那么代码层面其实就是取消对new的使用。 1.3应用实例 需要一辆汽…

【vue3学习笔记】shallowReactive与shallowRef;readOnly与shallowReadOnly;toRaw与markRaw

尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 课程 P158节 《shallowReactive与shallowRef》笔记&#xff1a; reactive()与shallowReactive()&#xff1a;reactive()处理后的数据是响应式的&#xff0c;对象内嵌套的深层结构全部是响应式的。shallowReactive()处理后的数据…

【C语言期末】商品管理系统

本文资源&#xff1a;https://download.csdn.net/download/weixin_47040861/88820155 1.题目要求 商品管理系统 商品信息包括&#xff1a;包括编号、类别、名称、价格、折扣比例、生产时间 、存货数量等要求&#xff1a;1、信息首先保存在文件中&#xff0c;然后打开文件进行…

Linux操作系统基础(三):虚拟机与Linux系统安装

文章目录 虚拟机与Linux系统安装 一、系统的安装方式 二、虚拟机概念 三、虚拟机的安装 四、Linux系统安装 1、解压人工智能虚拟机 2、找到解压目录中的node1.vmx 3、启动操作系统 虚拟机与Linux系统安装 一、系统的安装方式 Linux操作系统也有两种安装方式&#xf…

以“防方视角”观社工钓鱼攻击

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 案例概述02 攻击路径03 防方思路 01 案例概述 这篇文章来自奇安信攻防社区“小艾”&#xff0c;记录的某师傅通过社工钓鱼诱导企业员工点击含有木马的文件&#xff0c;侵入系统取得了终端控制权。接…

MyBatis之环境搭建以及实现增删改查

MyBatis之环境搭建以及实现增删改查 前言准备工作1.保证数据库已启动2. 创建Person表 MyBatis开发环境搭建1.下载MyBatis jar包2.下载MySQL的JDBC驱动3.新建Java工程&#xff08;Java8&#xff09;&#xff0c;导入MyBatis的jar包以及JDBC驱动 实现步骤1. 创建Peron类2. 编写Ma…

c语言--指针数组(详解)

目录 一、什么是指针数组&#xff1f;二、指针数组模拟二维数组 一、什么是指针数组&#xff1f; 指针数组是指针还是数组&#xff1f; 我们类比一下&#xff0c;整型数组&#xff0c;是存放整型的数组&#xff0c;字符数组是存放字符的数组。 那指针数组呢&#xff1f;是存放…

SpringCloud-Eureka原理分析

Eureka是Netflix开源的一款用于实现服务注册与发现的工具。在微服务架构中&#xff0c;服务的动态注册和发现是必不可少的组成部分&#xff0c;而Eureka正是为了解决这一问题而诞生的。 一、为何需要Eureka 在微服务架构中&#xff0c;服务之间的协同合作和高效通信是至关重要…

【性能最佳实践】跟着我们一起玩转查询模式与性能分析!

使用最新的驱动程序 MongoDB的官方驱动程序是由负责核心数据库开发的同一个专业团队打造的。这些驱动程序的更新通常比数据库本身更频繁&#xff0c;大概每几个月就会发布一次新版本。我们建议您尽可能使用最新版本的驱动程序&#xff0c;并在您使用的编程语言中安装可用的本地…

蓝桥杯Web应用开发-CSS3 新特性【练习三:文本阴影】

文本阴影 text-shadow 属性 给文本内容添加阴影的效果。 文本阴影的语法格式如下&#xff1a; text-shadow: x-offset y-offset blur color;• x-offset 是沿 x 轴方向的偏移距离&#xff0c;允许负值&#xff0c;必须参数。 • y-offset 是沿 y 轴方向的偏移距离&#xff0c…