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) returnwhile (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) returnwhile (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 : rejectsettled(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 : rejectsettled(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 : rejectsettled(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 = 0for (const item of promises) {i++}if (i === 0) resolve([])})
}

传入的值非Promise

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

static all(promises) {return new MyPromise((resolve, reject) => {let i = 0for (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 = 0let result = []let fulfilled = 0for (const item of promises) {let index = ii++MyPromise.resolve(item).then((data) => {result[index] = datafulfilled++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 = 0for (const item of promises) {i++Promise.resolve(item).then(resolve, reject)}if (i === 0) resolve([])})
}

完整的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)}}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 = 0let result = []let fulfilled = 0for (const item of promises) {let index = ii++MyPromise.resolve(item).then((data) => {result[index] = datafulfilled++if (fulfilled === i) resolve(result)}, reject)}if (i === 0) resolve([])})}static race(promises) {return new MyPromise((resolve, reject) => {let i = 0for (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) returnthis.#state = statethis.#value = valuethis.#run()}#handlersPush(onFulfilled, onRejected, resolve, reject) {this.#handlers.push({onFulfilled,onRejected,resolve,reject})}#run() {if (this.#state === MyPromise.#PENDING) returnwhile (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 : rejectsettled(this.#value)} else {try {const data = callback(this.#value)if (MyPromise.#isPromise(data)) data.then(resolve, reject)else resolve(data)} catch (error) {reject(error)}}})}
}

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

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

相关文章

在Visual Studio中引用和链接OpenSceneGraph (OSG) 库

在Visual Studio中引用和链接OpenSceneGraph (OSG) 库,按照以下步骤操作: 构建或安装OSG库 下载OpenSceneGraph源代码(如3.0版本)并解压。使用CMake配置项目,为Visual Studio生成解决方案文件。通常您需要设置CMake中的…

Office2013下载安装教程,保姆级教程,附安装包和工具

前言 Microsoft Office是由Microsoft(微软)公司开发的一套基于 Windows 操作系统的办公软件套装。常用组件有 Word、Excel、PowerPoint、Access、Outlook等。 准备工作 1、Win7 及以上系统 2、提前准备好 Office 2013 安装包 安装步骤 1.鼠标右击【Office2013(64bit)】压缩…

Vue中 常用的修饰符有哪些

Vue是一款建立在JavaScript框架上的开源前端库,已经成为当今前端开发人员最喜爱的选择之一。它的简洁语法和强大的功能使得开发者可以轻松地构建交互性的网页应用程序。在Vue中,修饰符是一个重要的概念,它们可以帮助我们更好地控制和定制DOM元…

PV、UV、IP

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言1. PV1.1 PV 计算1.2 PV 的影响因素 2. UV2.1 UV 计算2.2UV 的影响因素 3. IP3.1 IP和UV①UV大于IP②UV小于IP 三者的关系PV 和 UV 前言 PV、UV、IP是我们在运…

深度学习入门笔记(九)自编码器

自编码器是一个无监督的应用,它使用反向传播来更新参数,它最终的目标是让输出等于输入。数学上的表达为,f(x) x,f 为自编码器,x 为输入数据。 自编码器会先将输入数据压缩到一个较低维度的特征,然后利用这…

Vue3.0(五):Vue-Router 4.x详解

Vue-Router详解 vue-router教程 认识前端路由 路由实际上是网络工程中的一个术语 在架构一个网络的时候,常用到两个很重要的设备—路由器和交换机路由器实际上就是分配ip地址,并且维护着ip地址与电脑mac地址的映射关系通过映射关系,路由器…

读懂 FastChat 大模型部署源码所需的异步编程基础

原文:读懂 FastChat 大模型部署源码所需的异步编程基础 - 知乎 目录 0. 前言 1. 同步与异步的区别 2. 协程 3. 事件循环 4. await 5. 组合协程 6. 使用 Semaphore 限制并发数 7. 运行阻塞任务 8. 异步迭代器 async for 9. 异步上下文管理器 async with …

戴上HUAWEI WATCH GT 4,解锁龙年新玩法

春节将至,华为WATCH GT 4作为一款颜值和实力并存的手表,能为节日增添了不少趣味和便利。无论你是钟情于龙年表盘或定制属于自己的表盘,还是过年用来抢红包或远程操控手机拍全家福等等,它都能成为你的“玩伴”。接下来,…

宏观行业心得

OLAP的特点 电商这样的OLTP场景大家更熟悉。相比之下,OLAP的特点: 读相对多,1000row以上大批写入,不改已有数据查询时输出很多行、很少列,结果被过滤或聚合后能够在一台服务器的内存中单台服务器qps数百,…

二分算法--模板及原理总结

二分答案 首先我们看这个图: 我们需要二分的答案就是这个临界点x。 什么情况下可以使用二分呢: 具有单调性(单调递增,单调递减),二段性(整个区间一分为二,一段区间满足,一…

为什么许多年轻人不喜欢回农村过年了?

为什么许多年轻人不喜欢回农村过年了? 随着时代的变迁和社会的发展,越来越多的年轻人选择在春节期间留在城市,而不是回到农村老家过年。这一现象引起了人们的关注和思考:为什么许多年轻人不喜欢回农村过年了? 首先&a…

全栈笔记_插件篇(用Volar替换Vuter)

Volar与Vuter的区别 TS支持:Volar和Vuter是2个独立的插件,都是为.vue单文件组件提供代码高亮以及语法支持,但是Vuter对ts的支持并不友好。唯一根标签:Volar 不限制是否唯一根标签,vuter 则会报错 The template root r…

史上最“昂贵”的漏洞

阿丽亚娜 5 号”事故 欧洲航天局“阿丽亚娜 5 号”运载火箭在 1996 年 6 月 4 日首次发射时发生了事故。火箭在飞行的第 40 秒由于软件错误而解体并爆炸,该软件直接沿用了以前“阿丽亚娜 4 号”火箭的软件,且未在新环境中进行测试。 此次事故导致四颗卫…

SpringBoot响应式编程教程-WebFlux

SpringBoot响应式编程教程-WebFlux 前言正文一、Reactor1、核心概念2、核心特性 二、Spring Webflux与springmvc的组件对比 三、R2DBC 结语 前言 最近有个项目需要用到响应式编程,一开始还是很懵的,以为是网页的自适应,通过部分文章的学习&a…

【模板初阶】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3. 类模板 3.1 类模板的定义…

FPGA_工程_基于Rom的VGA图像显示

一 工程框图 框图中,CLK_in,Vga_ctrl,Vga_pic模块已有,只需要对顶层模块进行修改,并将rom ip例化添加到Vga_pic模块的.v文件中,对Vga_pic的.v文件进行一定修改。 二 理论补充 显示图像的方法:…

Mac 版 Excel 和 Windows 版 Excel的区别

Excel是一款由微软公司开发的电子表格程序,广泛应用于数据处理、分析和可视化等领域。它提供了丰富的功能和工具,包括公式、函数、图表和数据透视表等,帮助用户高效地处理和管理大量数据。同时,Excel还支持与其他Office应用程序的…

docker安装etherpad文档系统

效果 安装 1.创建并进入目录 mkdir -p /opt/etherpad cd /opt/etherpad 2.修改目录权限 chmod -R 777 /opt/etherpad 3.创建并启动容器 docker run -d --name etherpad --restart always -p 10054:9001 -v /opt/etherpad/data:/opt/etherpad-lite/var etherpad/etherpad:la…

【Java数据结构】ArrayList和LinkedList的遍历

一&#xff1a;ArrayList的遍历 import java.util.ArrayList; import java.util.Iterator; import java.util.List;/*** ArrayList的遍历*/ public class Test {public static void main(String[] args) {List<Integer> list new ArrayList<>();list.add(5);list…

win32编程系统BUG(Win32 API中的WM_SETTEXT消息)

由于频繁使用Win32 API中的WM_SETTEXT消息&#xff0c;导致内存占用直线上升。 暂未找到有效解决方案。