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 = null
    constructor(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") return
    this.#state = "fulfilled"
    this.#value = data
}
const reject = (reason) => {
    if (this.#state !== "pending") return
    this.#state = "rejected"
    this.#value = reason
}

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

class MyPromise {
    #state = "pending"
    #value = null
    constructor(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") return
        this.#state = state
        this.#value = value
    }
}

代码优化

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

class MyPromise {
    #state = "pending"
    #value = null
    static #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) return
        this.#state = state
        this.#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) return
        this.#state = state
        this.#value = value
        if (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) return
        this.#state = state
        this.#value = value
        this.#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) return
        while (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 = null
    static #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) return
        this.#state = state
        this.#value = value
        this.#run()

    }
    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(onFulfilled, onRejected, resolve, reject)
            this.#run()
        })
    }
    #run() {
        if (this.#state === MyPromise.#PENDING) return
        while (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(下)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FFmpeg中的Color颜色参数解析、转码和HDR

前言 视频中帧的颜色信息非常重要,表示着编码时用到的标准,意味着解码时也要对应上,或者要使用正确的转换函数,否则就会带来色差问题。 关于FFmpeg中的颜色参数,有下边几个重要的结构体: 颜色参数相关的结…

Git远程仓库的使用(Gitee)及相关指令

目录 1 远程仓库的创建和配置 1.1 创建远程仓库 1.2 设置SSH公钥 2 指令 2.1 git remote add 远端名称(一般为origin) 仓库路径 2.2 git remote 2.3 git push [-f] [--set-upstream] [远端名称 [本地分支名][:远端分支名]] 2.3 git clone url 2.4 git fetch 2.5 git p…

巴尔加瓦算法图解:算法运用(上)

目录 树反向索引傅立叶变换 并行算法MapReduce函数 树 如果能将用户名插入到数组的正确位置就好了,这样就无需在插入后再排序。为此,有人设计了一种名为二叉查找树(binary search tree)的数据结构。 每个node的children 都不大于两个。对于其中的每个…

微信小程序上传代码教程

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 小程序上传代码到gogs上面来 整体架构流程 小程序也要远程连接仓库,实现代码上传 技术名词解释 微信开发者工具gogs 技术细节 连接gogs仓库地址 微信小程序需要head将本地代码和gogs代码同步 小结 …

java学习(多态)

一、多态 含义:方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础上的。 多态的具体体现: 1)方法的多态 (例如重写和重载) 2)对象的多态 多态注意事项&#xff1…

SpringCloud--Gateway解析

一、Gateway简介 Gateway是Spring Cloud官方推出的第二代微服务网关,它旨在提供统一的路由方式以及为微服务应用提供强大的负载均衡能力。与第一代Spring Cloud Netflix Zuul相比,Spring Cloud Gateway在性能、可扩展性、易用性等方面都有了显著的提升。…

python web 框架Django学习笔记

2018年5月 python web 框架Django学习笔记 Django 架站的16堂课 MVC架构设计师大部分框架或大型程序项目中一种软件工程的架构模式,把程序或者项目分为三个主要组成部分,Model数据模型、View视图、Controller控制器。 命令及设置相关 创建数据库及中间…

使用Launch4j将jar包转成.exe可执行文件

Launch4j官网:Launch4j - Cross-platform Java executable wrapper 然后点击上面按钮 随便写个文件名

分享66个相册特效,总有一款适合您

分享66个相册特效,总有一款适合您 66个相册特效下载链接:https://pan.baidu.com/s/1jqctaho4sL_iGSNExhWB6A?pwd8888 提取码:8888 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气,收集整理更不…

FastDFS安装并整合Openresty

FastDFS安装并整合Openresty 一、安装环境准备【CentOS7.9】二、FastDFS--tracker安装2.1.下载fastdfs2.2.FastDFS安装环境2.3.安装FastDFS依赖libevent库2.4.安装libfastcommon2.5.安装 libserverframe 网络框架2.6.tracker编译安装2.7.安装之后文件目录介绍2.8.错误处理2.9.配…

Android SystemConfig相关

SystemConfig在哪里初始化 它声明在PackageManagerService类的静态方法main()中。在该方法中间定义Injector类对象时,作为它的构造参数。它是调用的SystemConfig.getInstance()实现初始化,之后能通过Injector类对象的getSystemConfig()得到SystemConfig类…

Python贝尔多项式

文章目录 Bell数和Bell多项式第二类Bell多项式 Bell数和Bell多项式 Bell,即所有包含 n n n个对象的有限集合的子集数之和,可通过递推式进行定义 B n ∑ k 0 n − 1 ( n − 1 k ) B k , B 0 1 B_n\sum^{n-1}_{k0}\begin{pmatrix} n-1\\k \end{pmatrix…

Select 选择器 el-option 回显错误 value

离谱 回显的内容不是 label 而是 value 的值 返回官方看说明: v-model的值为当前被选中的el-option的 value 属性值 value / v-model 绑定值有3种类型 boolean / string / number 根据自身代码猜测是:tableData.bookId 与 item.id 类型不一致导致 &…

LabVIEW伺服阀性能参数测试

LabVIEW伺服阀性能参数测试 伺服阀作为电液伺服系统中的核心元件,其性能参数的准确测试对保证系统整体性能至关重要。开发了一种基于LabVIEW软件开发的伺服阀性能参数测试系统,提高测试的自动化程度和精确性,同时降低操作复杂度和成本。 传…

python + numpy test

1. 2. What is the correct syntax to output the type of a variable or object in Python? Syntax of the Python type() functionThe type() function 3. upper() 全大写 lower() 全小写 4. 接下来是Numpy部分 1. What is a correct syntax to check the number of di…

一条 SQL 查询语句是如何执行的

MySQL 的基本架构示意图 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分 Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等…

YOLOv8算法改进【NO.101】引入最新的损失函数Focaler-IoU

前 言 YOLO算法改进系列出到这,很多朋友问改进如何选择是最佳的,下面我就根据个人多年的写作发文章以及指导发文章的经验来看,按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通: 第一…

VitePress-12-markdown中使用vue的语法

前言 VitePress 中,markdown文档最终都会转换成为 html文件,我们在访问的时候,也是直接访问的 xxx.html 文件。而且,markdown文档会被作为 [vue单文件] 进行处理,因此,我们我们可以在文档中使用 vue 语法&…

C#,雷卡曼数(Recamán Number)的算法与源代码

1 雷卡曼数(Recamn Number) 雷卡曼数(Recamn Number),即Recaman序列被定义如下: (1) a[0]0; (2) 如果a[m-1]-m>0并且这个值在序列中不存在,则a[m]a[m-1]-m; (3) 否则a[m]a[m-1]m; 雷卡曼序…

c#安全-nativeAOT

文章目录 前记AOT测试反序列化Emit 前记 JIT\AOT JIT编译器(Just-in-Time Complier),AOT编译器(Ahead-of-Time Complier)。 AOT测试 首先编译一段普通代码 using System; using System.Runtime.InteropServices; namespace co…