js手写Promise(上)

news2025/1/19 2:40:22

目录

  • 构造函数
    • 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(下)

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

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

相关文章

精简还是全能?如何在 Full 和 Lite 之间做出最佳选择!关于Configuration注解的Full模式与Lite模式(SpringBoot2)

🏃‍♂️ 微信公众号: 朕在debugger© 版权: 本文由【朕在debugger】原创、需要转载请联系博主📕 如果文章对您有所帮助,欢迎关注、点赞、转发和订阅专栏! 前言 关于 Configuration 注解,相信在座的各位 Javaer 都…

可达鸭二月月赛——基础赛第六场(周五)题解,这次四个题的题解都在这一篇文章内,满满干货,含有位运算的详细用法介绍。

姓名 王胤皓 T1 题解 T1 题面 T1 思路 样例输入就是骗人的&#xff0c;其实直接输出就可以了&#xff0c;输出 Hello 2024&#xff0c;注意&#xff0c;中间有一个空格&#xff01; T1 代码 #include<bits/stdc.h> using namespace std; #define ll long long int …

Swift 使用 Combine 管道和线程进行开发 从入门到精通八

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…

ANSI Escape Sequence 下落的方块

ANSI Escape Sequence 下落的方块 1. ANSI Escape 的用途 无意中发现 B站有人讲解&#xff0c; 完全基于终端实现俄罗斯方块。 基本想法是借助于 ANSI Escape Sequence 实现方方块的绘制、 下落动态效果等。对于只了解 ansi escape sequence 用于 log 的颜色打印的人来说&…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第10章 项目进度管理(四)

博主2023年11月通过了信息系统项目管理的考试&#xff0c;考试过程中发现考试的内容全部是教材中的内容&#xff0c;非常符合我学习的思路&#xff0c;因此博主想通过该平台把自己学习过程中的经验和教材博主认为重要的知识点分享给大家&#xff0c;希望更多的人能够通过考试&a…

【Java EE】----SpringBoot的日志文件

1.SpringBoot使用日志 先得到日志对象通过日志对象提供的方法进行打印 2.打印日志的信息 3.日志级别 作用&#xff1a; 可以筛选出重要的信息不同环境实现不同日志级别的需求 ⽇志的级别分为&#xff1a;&#xff08;1-6级别从低到高&#xff09; trace&#xff1a;微量&#…

高级数据结构与算法 | 布谷鸟过滤器(Cuckoo Filter):原理、实现、LSM Tree 优化

文章目录 Cuckoo Filter基本介绍布隆过滤器局限变体 布谷鸟哈希布谷鸟过滤器 实现数据结构优化项Victim Cache备用位置计算半排序桶 插入查找删除 应用场景&#xff1a;LSM 优化 Cuckoo Filter 基本介绍 如果对布隆过滤器不太了解&#xff0c;可以看看往期博客&#xff1a;海量…

CentOS 7安装Nodejs

说明&#xff1a;本文介绍如何在云服务器上CentOS 7操作系统上安装Nodejs。以及安装过程中遇到的问题。 下载压缩包&解压 首先&#xff0c;先去官网下载Linux版本的Node。 将下载下来的压缩包&#xff0c;上传到云服务器上&#xff0c;解压。配置环境变量。 &#xff08…

VScode为什么选择了Electron,而不是QT?

选择Electron而不是QT可能是基于以下几个原因&#xff1a; Web技术的普及和开发者生态系统&#xff1a;Web技术如HTML、CSS和JavaScript在开发者中非常普及&#xff0c;开发者生态系统庞大且活跃。使用Electron可以利用这些熟悉的Web技术和丰富的开发者社区资源。跨平台支持&am…

蓝桥杯(Web大学组)2022国赛真题:水果消消乐

思路&#xff1a; 记录点击次数&#xff0c;点击次数为1时&#xff0c;记录点击下标&#xff08;用于隐藏or消除&#xff09;、点击种类&#xff0c;点击次数为2时&#xff0c;判断该下标所对应种类与第一次是否相同 相同&#xff1a;两个都visibility:hidden &#xff08;占…

黄金交易策略(EA):三个仓位的设计是确保可以不停息做单

完整EA&#xff1a;Nerve Knife.ex4黄金交易策略_黄金趋势ea-CSDN博客

MacOS 查AirPods 电量技巧:可实现低电量提醒、自动弹窗

要怎么透过macOS 来查询AirPods 电量呢&#xff1f;当AirPods 和Mac 配对后&#xff0c;有的朋友想通过Mac来查询AirPods有多少电量&#xff0c;这个里有几个技巧&#xff0c;下面我们来介绍一下。 透过Mac 查AirPods 电量技巧 技巧1. 利用状态列上音量功能查询 如要使用此功能…

在django中集成markdown文本框

首先需要下载开源组件&#xff1a;http://editor.md.ipandao.com/&#xff0c;可能需要挂梯子。 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1D9o3P8EQDqSqfhAw10kYkw 提取码&#xff1a;eric 1.在html代码中生成一个div&#xff0c;ideditor <div c…

【玩转408数据结构】线性表——定义和基本操作

考点剖析 线性表是算法题命题的重点&#xff0c;该类题目实现相对容易且代码量不高&#xff0c;但需要最优的性能&#xff08;也就是其时间复杂度以及空间复杂度最优&#xff09;&#xff0c;这样才可以获得满分。所以在考研复习中&#xff0c;我们需要掌握线性表的基本操作&am…

Peter算法小课堂—枚举优化

哈哈哈&#xff0c;新年快乐&#xff01;这一次Peter将要给大家讲一讲轻松、摆烂的算法—枚举&#xff01;咋就是说呀&#xff0c;枚举这个玩意我语法就会了。但大家想想&#xff0c;咱们CSP考试时&#xff08;除了没过初赛的&#xff09;只给1秒&#xff0c;大家想想&#xff…

跟着cherno手搓游戏引擎【23】项目维护、2D引擎之前的一些准备

项目维护&#xff1a; 修改文件结构&#xff1a; 头文件自己改改就好了 创建2DRendererLayer&#xff1a; Sandbox2D.h: #pragma once #include "YOTO.h" class Sandbox2D :public YOTO::Layer {public:Sandbox2D();virtual ~Sandbox2D() default;virtual void O…

【lesson46】进程通信之system V(共享内存)

文章目录 共享内存通信原理用共享内存通信shmServer.ccshmClient.cc 完整通信代码common.hppLog.hppshmServer.ccshmClient.cc通信测试 共享内存借助管道添加访问控制common.hppshmServer.ccshmClient.cc 共享内存通信原理 两个进程将一块system V的物理地址通过页表映射到自己…

JSP编程

JSP编程 您需要理解在JSP API的类和接口中定义的用于创建JSP应用程序的各种方法的用法。此外,还要了解各种JSP组件,如在前一部分中学习的JSP动作、JSP指令及JSP脚本。JSP API中定义的类提供了可借助隐式对象通过JSP页面访问的方法。 1. JSP API的类 JSP API是一个可用于创建…

大模型学习 一

https://www.bilibili.com/video/BV1Kz4y1x7AK/?spm_id_from333.337.search-card.all.click GPU 计算单元多 并行计算能力强 指数更重要 A100 80G V100 A100 海外 100元/时 单卡 多卡并行&#xff1a; 单机多卡 模型并行 有资源的浪费 反向传播 反向传播&#xff08;B…

第6章 智能租房——前期准备

学习目标 了解智能租房项目&#xff0c;能够说出项目中各模块包含的功能 熟悉智能租房项目的开发模式与运行机制&#xff0c;能够复述项目的开发模式与运行机制 掌握智能租房项目的创建&#xff0c;能够独立创建智能租房项目 掌握智能租房项目的配置&#xff0c;能够为智能租…