js手写Promise(下)

目录

  • resolve与reject的调用时机
    • 封装优化
  • 回调返回Promise
    • isPromise
    • 手动调用then
  • 微队列
  • catch
  • resolve
  • reject
  • all
    • 传入的序列为空
    • 传入的值非Promise
  • race
  • 完整的Promise代码

如果没有看过上半部分的铁铁可以看看这篇文章
js手写Promise(上)

resolve与reject的调用时机

Promisethen中,我们需要解决两个问题,一个是onFulfilled和onRejected什么时候调用,一个是resolve和reject什么时候调用,第一个问题我们在上篇文章中解决了,现在我们需要解决第二个问题
具体而言,什么时候调用resolvereject分为两种情况

  1. 传入的参数不是函数
    因为我们已经提前将resolvereject传递到了handlers中,所以我们可以在run中处理相关逻辑

    #run() {
    	if (this.#state === MyPromise.#PENDING) return
     	while (this.#handlers.length > 0) {
        	const handler = this.#handlers.shift()
        	if (this.#state === MyPromise.#FULFILLED) {
            	if (typeof handler.onFulfilled !== "function") {
                	handler.resolve(this.#value)
            	} else {
                	handler.onFulfilled(this.#value)
            	}
        	}
        	else if (this.#state === MyPromise.#REJECTED) {
            	if (typeof handler.onRejected !== "function") {
                	handler.reject(this.#value)
            	} else {
                	handler.onRejected(this.#value)
            	}
        	}
    	}
    }
    

    如果传入的参数不是函数的话,那then返回的Promise穿透了,状态与调用then的Promise实例状态一致,如果调用then的实例状态为fulfilledthen返回的实例就调用resolve,反之亦然

  2. 传入的参数是函数
    如果传入的参数是函数,我们就需要判断在执行函数的时候有没有报错,如果没有就代表函数执行成功,调用resolve,否则调用reject

    #run() {
    if (this.#state === MyPromise.#PENDING) return
    while (this.#handlers.length > 0) {
        const handler = this.#handlers.shift()
        if (this.#state === MyPromise.#FULFILLED) {
            if (typeof handler.onFulfilled !== "function") {
                handler.resolve(this.#value)
            } else {
                try {
                    const data = handler.onFulfilled(this.#value)
                    handler.resolve(data)
                } catch (error) {
                    handler.reject(error)
                }
            }
        }
        else if (this.#state === MyPromise.#REJECTED) {
            if (typeof handler.onRejected !== "function") {
                handler.reject(this.#value)
            } else {
                try {
                    const data = handler.onRejected(this.#value)
                    handler.resolve(data)
                } catch (error) {
                    handler.reject(error)
                }
            }
        }
    }
    }
    

封装优化

这样问题就解决了,但是我们发现函数中有许多重复代码,我们可以将这些代码封装成一个函数

#run() {
    if (this.#state === MyPromise.#PENDING) return
    while (this.#handlers.length > 0) {
        const handler = this.#handlers.shift()
        if (this.#state === MyPromise.#FULFILLED) {
            this.#runOne(handler.onFulfilled, handler.resolve, handler.reject)
        }
        else if (this.#state === MyPromise.#REJECTED) {
            this.#runOne(handler.onRejected, handler.resolve, handler.reject)
        }
    }
}
#runOne(callback, resolve, reject) {
    if (typeof callback !== "function") {
        const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
        settled(this.#value)
    } else {
        try {
            const data = callback(this.#value)
            resolve(data)
        } catch (error) {
            reject(error)
        }
    }
}

回调返回Promise

这种情况比较特殊,如果返回的结果是一个Promise的话调用resolve还是reject由这个新的Promise实例决定,我们只需要手动调用它的then方法

isPromise

在调用它的then方法之前我们还需要判断返回的结果是不是一个PromisePromiseA+规范规定,对象或是函数,如果存在then方法就是Promise,所以我们可以写出这么一个辅助函数

class MyPromise {
    static #isPromise(promise) {
        if (typeof promise === "function" || typeof promise === "object") {
            if (typeof promise.then === "function") return true
        }
        return false
    }
}

手动调用then

现在我们就可以手动调用then方法了

#runOne(callback, resolve, reject) {
    if (typeof callback !== "function") {
        const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
        settled(this.#value)
    } else {
        try {
            const data = callback(this.#value)
            if (MyPromise.#isPromise(data)) data.then(resolve, reject)
            else resolve(data)
        } catch (error) {
            reject(error)
        }
    }
}

微队列

至此我们的then方式实现的差不多了,还有一个小细节就是,then方法里的任务是放入微任务队列里执行的,所以我们还需要封装一个函数用于将任务放入微任务队列

#runOne(callback, resolve, reject) {
    MyPromise.#mircoTask(() => {
        if (typeof callback !== "function") {
            const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
            settled(this.#value)
        } else {
            try {
                const data = callback(this.#value)
                if (MyPromise.#isPromise(data)) data.then(resolve, reject)
                else resolve(data)
            } catch (error) {
                reject(error)
            }
        }
    })
}
static #mircoTask(callback) {
    if (typeof window === "object" && MutationObserver) {
        const observer = new MutationObserver(callback)
        const p = document.createElement('p')
        observer.observe(p, { childList: true })
        p.innerText = '1'
    } else if (typeof global === "object" && process) {
        process.nextTick(callback)
    } else {
        setTimeout(callback, 0)
    }
}

如果想要把一个任务放入微任务队列需要根据环境分类处理,具体来说有以下几种情况

  1. node环境
    node环境里有一个api叫processprocess.nextTick能将一个任务放入微任务队列中
  2. 浏览器环境
    浏览器中有一个观察器叫MutationObserver,它用于观察一个元素是否变化,如果变化了就将预先设定好的函数放入微任务队列
  3. 其他环境
    如果宿主环境既不是node也不是浏览器,或者不支持MutationObserverprocess,那么就只能通过setTimeOut来模拟微队列了

最后我们来测试一下我们的then方法

let p1 = new MyPromise((resolve, reject) => {
    reject(123)
})
p1.then((res) => {
    console.log("1resolve" + res)
}, (err) => {
    console.log("1reject" + err)
})
p1.then((res) => {
    console.log("2resolve" + res)
}, (err) => {
    return new MyPromise((resolve, reject) => {
        resolve(err)
    })
}).then((res) => {
    console.log("3resolve" + res)
}, (err) => {
    console.log("3reject" + err)
})

结果

catch

catch的实现与then类似,都是向handlers里放入回调,只不过catch放入的回调中onFulfilledundefined

class MyPromise {
    catch(onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(undefined, onRejected, resolve, reject)
            this.#run()
        })
    }
}

我们来测试一下

let p1 = new MyPromise((resolve, reject) => {
    reject(123)
})
p1.catch((err) => {
    console.log(err)
})
let p2 = new MyPromise((resolve, reject) => {
    reject(456)
})
p2.then(null, (err) => {
    return new MyPromise((resolve, reject) => {
        reject(789)
    })
}).catch(err => {
    console.log(err)
})

结果

resolve

这里我们要实现的resolvePromise类方法,回忆我们之前使用Promise的经验,如果resolve中传递的不是Promise,那么Promise会将其包装成一个Promise返回,如果传入的是一个Promise,那么会调用它的then方法,知道了这些我们的代码就可以这么写

static resolve(value) {
    return new MyPromise((resolve, reject) => {
        if (MyPromise.#isPromise(value)) value.then(resolve, reject)
        else resolve(value)
    })
}

我们来测试一下

MyPromise.resolve(1).then(console.log)
MyPromise.resolve(new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
})).then(console.log)

结果

reject

rejectresolve类似,只不过resolve是调用resolve方法,而reject是调用reject方法

static reject(reason) {
    return new MyPromise((resolve, reject) => {
        if (MyPromise.#isPromise(reason)) reason.then(resolve, reject)
        else reject(reason)
    })
}

因为和resolve类似,所以我就不测试了

all

all也是Promise的一个类方法,我们需要向all里传入一个参数,表示为一系列Promise的序列,可以是set,也可以是数组all也是返回一个Promise,知道了这些后我们就能将all的声明写出来

static all(promises) {
    return new MyPromise((resolve, reject) => {
        
    })
}

那么现在的问题就是我们什么时候调用resolvereject

传入的序列为空

如果传入的序列为空就没什么好说的了,直接resolve([])就行,那怎么判断序列是否为空呢,定义一个长度变量,我们可以通过for...of来遍历序列,每遍历一次长度变量自增,遍历完后如果长度变量依旧为0表示序列为空

static all(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        for (const item of promises) {
            i++
        }
        if (i === 0) resolve([])
    })
}

传入的值非Promise

因为我们并不确定拿到的东西是否是一个Promise,所以我们需要使用Promise.resolve将它包裹起来

static all(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        for (const item of promises) {
            i++
            MyPromise.resolve(item).then()
        }
        if (i === 0) resolve([])
    })
}

根据Promiseall的逻辑,如果序列中有一个失败,那all返回的Promise的状态就是失败
如果当前Promise的结果为成功的话则需要做两件事,一件事汇总结果,一件事判断Promise是否全部完成
因为all要求返回的结果与传递的序列顺序要求一致,所以在汇总结果时不能使用push,而是应该使用下标
我们每完成一个Promise就记一次数,只要这个数字和我们之前统计的长度变量相同,就代表着这一串Promise执行结束,可以resolve
所以我们的代码可以写成这个样子

static all(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        let result = []
        let fulfilled = 0
        for (const item of promises) {
            let index = i
            i++
            MyPromise.resolve(item).then((data) => {
                result[index] = data
                fulfilled++
                if (fulfilled === i) resolve(result)
            }, reject)
        }
        if (i === 0) resolve([])
    })
}

我们来测试一下

let p1 = new MyPromise((resolve, reject) => {
    resolve(1)
})
let p2 = new MyPromise((resolve, reject) => {
    reject(2)
})
MyPromise.all([]).then(console.log)
MyPromise.all([p1, p2]).then(console.log).catch(err => {
    console.log("err" + err)
})
MyPromise.all([p1, 2, 3, 4]).then(console.log)

结果

race

raceall都是Promise的类方法,实现思路也是大同小异,all是等待全部Promise的结果,race是只要有一个Promise有结果就行,代码如下

static race(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        for (const item of promises) {
            i++
            Promise.resolve(item).then(resolve, reject)
        }
        if (i === 0) resolve([])
    })
}

完整的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)
        }
    }
    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(onFulfilled, onRejected, resolve, reject)
            this.#run()
        })
    }
    catch(onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(undefined, onRejected, resolve, reject)
            this.#run()
        })
    }
    static resolve(value) {
        return new MyPromise((resolve, reject) => {
            if (MyPromise.#isPromise(value)) value.then(resolve, reject)
            else resolve(value)
        })
    }
    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            if (MyPromise.#isPromise(reason)) reason.then(resolve, reject)
            else reject(reason)
        })
    }
    static all(promises) {
        return new MyPromise((resolve, reject) => {
            let i = 0
            let result = []
            let fulfilled = 0
            for (const item of promises) {
                let index = i
                i++
                MyPromise.resolve(item).then((data) => {
                    result[index] = data
                    fulfilled++
                    if (fulfilled === i) resolve(result)
                }, reject)
            }
            if (i === 0) resolve([])
        })
    }
    static race(promises) {
        return new MyPromise((resolve, reject) => {
            let i = 0
            for (const item of promises) {
                i++
                Promise.resolve(item).then(resolve, reject)
            }
            if (i === 0) resolve([])
        })
    }
    static #mircoTask(callback) {
        if (typeof window === "object" && MutationObserver) {
            const observer = new MutationObserver(callback)
            const p = document.createElement('p')
            observer.observe(p, { childList: true })
            p.innerText = '1'
        } else if (typeof global === "object" && process) {
            process.nextTick(callback)
        } else {
            setTimeout(callback, 0)
        }
    }
    static #isPromise(promise) {
        if (typeof promise === "function" || typeof promise === "object") {
            if (typeof promise.then === "function") return true
        }
        return false
    }
    #changeState(state, value) {
        if (this.#state !== MyPromise.#PENDING) return
        this.#state = state
        this.#value = value
        this.#run()
    }
    #handlersPush(onFulfilled, onRejected, resolve, reject) {
        this.#handlers.push({
            onFulfilled,
            onRejected,
            resolve,
            reject
        })
    }
    #run() {
        if (this.#state === MyPromise.#PENDING) return
        while (this.#handlers.length > 0) {
            const handler = this.#handlers.shift()
            if (this.#state === MyPromise.#FULFILLED) {
                this.#runOne(handler.onFulfilled, handler.resolve, handler.reject)
            }
            else if (this.#state === MyPromise.#REJECTED) {
                this.#runOne(handler.onRejected, handler.resolve, handler.reject)
            }
        }
    }
    #runOne(callback, resolve, reject) {
        MyPromise.#mircoTask(() => {
            if (typeof callback !== "function") {
                const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
                settled(this.#value)
            } else {
                try {
                    const data = callback(this.#value)
                    if (MyPromise.#isPromise(data)) data.then(resolve, reject)
                    else resolve(data)
                } catch (error) {
                    reject(error)
                }
            }
        })
    }
}

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

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

相关文章

GEE Colab——如何利用Matplotlib在colab中进行图形制作

在colab中绘制图表 笔记本的一个常见用途是使用图表进行数据可视化。Colaboratory 提供多种图表工具作为 Python 导入,让这一工作变得简单。 Matplotlib Matplotlib 是最常用的图表工具包,详情请查看其文档,并通过示例获得灵感。 线性图 线性图是一种常见的图表类型,用…

ELAdmin 的 CRUD

数据表结构 弄个测试的数据表,不同类型的几个字段,表名位 mp_reply。 生成代码 ELAdmin 可以自动生成代码。 左侧目录系统工具–代码生成,点开以后可以看到上面创建的数据表mp_reply,点击配置。 进入的页面内容有两部分&#…

88 SRC挖掘-拿下CNVD证书开源闭源售卖系统

目录 1.开源系统、闭源系统、售卖系统2.如何寻找上述三类系统并进行安全测试3.如何挑简单的入手最快速度获取证书装x演示案例:某开源逻辑审计配合引擎实现通用某闭源审计或黑盒配合引擎实现通用某售卖审计或黑盒配合引擎实现通用 涉及资源&am…

[职场] 进入大数据领域需要掌握哪些软件 #其他#职场发展#职场发展

进入大数据领域需要掌握哪些软件 学习大数据首先我们要学习Java语言和Linux操作系统,这两个是学习大数据的基础,学习的顺序不分前后。 Java 大家都知道Java的方向有JavaSE、JavaEE、JavaME,学习大数据要学习那个方向呢? 只需要学习Java的…

docker 开放tcp连接供idea等其他外部工具开放使用

docker 开放tcp连接供idea等其他外部工具开放使用 方法一:通过systemd工具 sudo systemctl edit docker.service 修改文件内容如下 ExecStart/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 重启 systemctl 配置 sudo systemctl daemon-reload 重启docker服务 s…

Redis核心技术与实战【学习笔记】 - 26.Redis数分布优化(应对数据倾斜问题)

简述 在切片集群中,数据会按照一定的规则分散到不同的实例上保存。比如,Redis Cluster 或 Codis 会先按照 CRC 算法的计算值对 Slot(逻辑槽)取模,同时 Slot 又有运维管理员分配到不同的实例上。这样,数据就…

Java:内部类、枚举、泛型以及常用API --黑马笔记

内部类 内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。 当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时&a…

C#,聚会数(相遇数,Rencontres Number)的算法与源代码

1 相遇数 相遇数(Rencontres Number,partial derangement numbers)是指部分扰动的数量,或与独立对象的r相遇的置换数(即具有固定点的独立对象的置换数)。 看不通。懂的朋友给解释一下哈。 2 源程序 using…

MySQL学习记录——칠 表操作

文章目录 1、了解2、创建和插入1、基本创建和插入2、插入并更新on duplicate3、插入并替换replace 3、Retrieve1、查询select2、条件查询where3、结果排序order by4、限制行数limit 4、更新Update5、删除delete6、去重7、聚合函数(5个)1、count2、sum3、…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之StepperItem组件

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之StepperItem组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、StepperItem组件 用作Stepper组件的页面子组件。 子组件 无。 接口 St…

【QT+QGIS跨平台编译】之三十一:【FreeXL+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、FreeXL介绍二、文件下载三、文件分析四、pro文件五、编译实践一、FreeXL介绍 【FreeXL跨平台编译】:Windows环境下编译成果(支撑QGIS跨平台编译,以及二次研发) 【FreeXL跨平台编译】:Linux环境下编译成果(支撑QGIS跨平台编译,以及二次研发) 【FreeXL跨平台…

openGauss学习笔记-216 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-CPU

文章目录 openGauss学习笔记-216 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-CPU216.1 CPU216.2 查看CPU状况216.3 性能参数分析 openGauss学习笔记-216 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-CPU 获取openGauss节点的CPU、内存、I/O和网络资源使用情况…

Unity3d Shader篇(六)— BlinnPhong高光反射着色器

文章目录 前言一、BlinnPhong高光反射着色器是什么?1. BlinnPhong高光反射着色器的工作原理2. BlinnPhong高光反射着色器的优缺点优点缺点 3. 公式 二、使用步骤1. Shader 属性定义2. SubShader 设置3. 渲染 Pass4. 定义结构体和顶点着色器函数5. 片元着色器函数 三…

保护我方水晶,2024 数据库安全工具盘点

在数据价值堪比石油的数字时代,对每个组织而言,保护这一核心资产显得尤为重要。无论是来自外部的黑客攻击和恶意软件,还是源于内部的人为失误和内鬼行为,威胁无处不在。本文将介绍几款先进的数据库安全工具,从不同维度…

C++ 水仙花数

案例描述:水仙花数是指一个3位数,它的每个位上的数字的3次幂之和等于它本身例如: 1A35A33A3153 请利用do.…while语句,求出所有3位数中的水仙花数 分析思路: 1、将所有的三位数进行输出(100~999&#x…

构造 蓝桥OJ小蓝的无限集

样例输入 4 1 4 7 2 5 8 3 6 8 12 11 81 样例输出 No Yes No No #include<bits/stdc.h> using namespace std;using ll long long;bool rnk(ll a, ll b, ll n) {if((n-1) % b 0) return true;else if (a 1) return false;ll res 1;while(res < n){res * a;if (r…

第三百一十回

我们在上一章回中介绍了"再谈ListView中的分隔线"&#xff0c;本章回中将介绍showMenu的用法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在第一百六十三回中介绍了showMenu相关的内容&#xff0c;它主要用来显示移动PopupMenu在页面中的位置…

L1-095 分寝室

一、题目 二、解题思路 遍历所有情况&#xff0c;i 为女生寝室数量&#xff0c;n-i 为男生寝室数量&#xff0c;循环的结束条件为不允许单人住一间寝室 所有待分配的学生都必须分到一间寝室&#xff1a;i<n0 && n-i<n1 && n-i>0 &#xff1b;对每种…

javaEE - 21( 15000字 Tomcat 和 HTTP 协议入门 -2)

一&#xff1a; HTTP 响应 1.1 认识 “状态码” (status code) 状态码表示访问一个页面的结果. (是访问成功, 还是失败, 还是其他的一些情况…)&#xff0c;以下为常见的状态码. 1.1.1 200 OK 这是一个最常见的状态码, 表示访问成功. 抓包抓到的大部分结果都是 200 HTTP/…

STM32控制JQ8400语音播报模块

时间记录&#xff1a;2024/2/7 一、JQ8400引脚介绍 标示说明ONE LINE一线操作引脚BUSY忙信号引脚&#xff0c;正在播放语音时输出高电平RX串口两线操作接收引脚TX串口两线操作发送引脚GND电源地引脚DC-5V电源引脚&#xff0c;3.3-5VDAC-RDAC输出右声道引脚DAC-LDAC输出左声道…