手写 PromiseA+ 实现,轻松通过 872 条用例

news/2024/12/27 19:48:53/文章来源:https://www.cnblogs.com/gupingan/p/18636607

手写 Promise/A+ 实现,轻松通过 872 条用例

规范参考:Promise/A+ 规范 - 中文版本

测试工具:https://github.com/promises-aplus/promises-tests

前言

从接触 Promise 到现在,笔者经历了这么个过程:

  1. 了解各种 Promise 规范,包括 Promise/A+,但对其具体内容不甚了解。
  2. 研究前人的 Promise 实现,自己尝试编写,但测试时经常遇到问题。
  3. 苦思冥想,为什么返回值能够决定 Promise 的状态?为什么链式调用要返回一个新的 Promise 而不是 this?我陷入了深深的困惑。
  4. 可能是由于执着,我深入研读了 Promise/A+ 规范。尽管起初对一些条款感到困惑,但我依然坚持学习。
  5. 逐句翻译、理解规范,逐渐熟悉了它。从最初需要对照规范编写代码,到如今能够手写 Promise,这个过程充满了乐趣,也让我对 Promise 的一些常用方法有了更深的理解。

如果你也在学习 Promise,遇到困难,我建议你对照规范和代码来理解。

实现

在阅读代码之前,你需要明白,代码中的注释并非随意添加,每条注释都对应着 Promise/A+ 规范中的具体条款。

关于规范,它主要包括以下几个部分:

  1. 专业术语,你可以简单了解即可。
  2. 详细规范,需要逐行理解,包括:
    • 2.1 Promise 的 3 种状态:pending(待定)、fulfilled(已实现)、rejected(已拒绝)。
    • 2.2 Promise 的 then 方法的实现,不同状态下应执行的操作。
    • 2.3 Promise 对某值的决策行为,也称为 Promise 解决过程。

因此,你会在注释中看到类似以下的标记:

// 2.1 (2)(2)

它表示,规范对应的 2.1 Promise 状态 下的第 2 个序号下的 第 2 条内容

标题序号 内容序号...

下方代码中尽管没有涵盖所有规范条款的注释,但是隐式实现了。

完整代码如下:

function MyPromise(executor) {this.state = 'pending' // 2.1 (1)(1) 初始状态,可以转变为其它两种状态,也就是已实现(fulfilled)或已拒绝(rejected)this.value = null // 2.1 (2)(2) 必须有一个不可改变的值this.reason = null // 2.1 (3)(2) 必须有一个不可改变的原因this.onFulfilledCallbacks = []this.onRejectedCallbacks = []const resolve = (value) => {if (this.state !== 'pending') returnthis.state = 'fulfilled'this.value = value// 2.2 (6)(1) 当 promise 被实现时,所有相应的 onFulfilled 回调必须按它们调用 then 的顺序执行this.onFulfilledCallbacks.forEach((callback) => callback(this.value))}const reject = (reason) => {if (this.state !== 'pending') returnthis.state = 'rejected'this.reason = reason// 2.2 (6)(2) 当 promise 被拒绝时,所有相应的 onRejected 回调必须按它们调用 then 的顺序执行this.onRejectedCallbacks.forEach((callback) => callback(this.reason))}// 如果 then 中对返回的 Promise 执行器做了异常处理,此步可选try {executor(resolve, reject)} catch (e) {reject(e)}
}// 2.2 (6) 原型上方法,根据不同状态保证同一个 Promise 上的 then 可被多次调用
MyPromise.prototype.then = function (onFulfilled, onRejected) {// 2.2 (1) onFulfilled 和 onRejected 参数可选,若不是函数则忽略(此处略微改造,本质上还是符合规范的,返回值或抛出异常决策链调用)const realOnFulfilled =typeof onFulfilled === 'function'? onFulfilled // 2.2 (2): (value) => {return value}const realOnRejected =typeof onRejected === 'function'? onRejected // 2.2 (3): (reason) => {throw reason}// 2.2 (7) then 必须返回一个 promiselet promise2 = new MyPromise((resolve, reject) => {if (this.state === 'fulfilled') {// 2.2 (4) 必须在执行上下文栈仅包含平台代码时才被调用queueMicrotask(() => {try {// 2.2 (5) 必须作为函数调用let x = realOnFulfilled(this.value)// 2.2 (7)(1) 根据返回值 x 运行 Promise 解决过程 [[Resolve]](promise2, x),此处也内含了规范 2.2 (7)(3) 处理resolvePromise(promise2, x, resolve, reject)} catch (e) {// 2.2 (7)(2) 抛出异常 e,则 promise2 必须以 e 作为原因被拒绝,此处也包含了 2.2 (7)(4) 的处理(非函数时上方默认函数抛出 reason,这里捕捉拒绝,不就是实现了吗)reject(e)}})} else if (this.state === 'rejected') {queueMicrotask(() => {try {let x = realOnRejected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}})} else if (this.state === 'pending') {// 如果 Promise 中的异步执行在后,then添加在前,该步骤能保证回调不被忽略,参考观察者 or 发布订阅?this.onFulfilledCallbacks.push(() => {queueMicrotask(() => {try {let x = realOnFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}})})this.onRejectedCallbacks.push(() => {queueMicrotask(() => {try {let x = realOnRejected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}})})}})// 2.2 (7)return promise2
}function resolvePromise(promise, x, resolve, reject) {// 2.3 (1) 如果 promise 和 x 指向同一个对象,则以 TypeError 拒绝 promise 作为原因,这里抛出或者 return reject 均可if (promise === x)throw new TypeError('promise and return value are the same')// 根据 Promise 或者 thenable 对象特性,可以直接判断分支如下if (x !== null && (typeof x === 'function' || typeof x === 'object')) {// 2.3 (3)(3)(3) 和 2.3 (3)(4)(1) 调用优先问题专用变量let called = falsetry {const then = x.thenif (typeof then === 'function') {// 2.3 (3)(3) 如果 then 是一个函数,则以 x 作为 this 调用它then.call(x,(y) => {if (called) returncalled = true// 2.3 (3)(3)(1)resolvePromise(promise, y, resolve, reject)},(r) => {if (called) returncalled = true// 2.3 (3)(3)(2)reject(r)})} else {// 2.3 (3)(4) x 为非 thenable 对象(如果 then 不是一个函数,则用 x 来实现 promise)resolve(x)}} catch (e) {if (called) returncalled = true// 2.3 (3)(2) 如果检索属性 x.then 时抛出异常 e,则以 e 作为原因拒绝 promise// 2.3 (3)(3)(4)(2) 如果调用 then 时抛出异常,以 e 作为原因拒绝 promisereject(e)}} else {// 2.3 (4) 如果 x 既不是对象也不是函数,则用 x 来实现 promise// x 为 null undefined、基本数值等情况resolve(x)}
}// 暴露一个接口提供测试的静态方法
MyPromise.deferred = function () {const result = {}result.promise = new MyPromise((resolve, reject) => {result.resolve = resolveresult.reject = reject})return result
}module.exports = MyPromise

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

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

相关文章

C#使用Tesseract C++ API过程记录

Tesseract Tesseract 是一个开源的光学字符识别(OCR)引擎,最初由 Hewlett-Packard(惠普)实验室开发,后来由 Google 收购并继续维护和开源贡献。Tesseract 可以识别多种语言的文字,广泛应用于将图片或扫描文档中的文本内容转换成可编辑的文本格式。随着深度学习技术的发展…

[攻防世界]信号不好先挂了

[攻防世界]信号不好先挂了[攻防世界]信号不好先挂了 分析 又是图片隐写我也先挂了…… 解题save bin 保存后的zip还需要修复一下才能解压缩……怎么里面又是这张图片 Misc隐写术 - Scr1pt? - 博客园两张一样图片还可在stegslove合成图片 用BlindWaterMark这个工具一直报错(麻…

10. 组合框控件

一、组合框控件组合框控件主要以列表形式为用户提供选择的项目,用户可以从中选择项。PySide6 中常用的列表类控件主要有 QComboBox(下拉组合框控件)、QFontComBox(字体组合框控件)。我们可以在终端中使用 pip 安装 pyside6 模块。 pip install pyside6二、下拉组合框控件下…

git review错误: is not registered in your account, and you lack forge committer permission

肉眼看上去,远端的邮箱和自己输入的邮箱是一致的 罪魁祸首是 git commit --amend 里面的邮箱带了中文引号,导致本地和远端邮箱名称不一致 从git review 命令报错email address那一行的奇怪字符可以看出端倪如上图所示,引号不是标准的linux字符

2.1基本选择器

选择器: 作用:选择页面上的某一个或者某一类元素 2.1基本选择器:1.标签选择器:选择一类标签 2.类选择器 class:选中所有class属性一致的标签(可以跨标签) .class名称{} 3.id选择器:id全局唯一 #id名称{} 不遵循就近原则:id>class>标签

痞子衡嵌入式:MCUXpresso for VS Code开发环境搭建及SDK工程导入

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是MCUXpresso for VS Code开发环境搭建及SDK工程导入。MCUXpresso IDE(包括其前身 LPCXpresso IDE、Kinetis Design Studio)是恩智浦软件团队持续开发了十多年的免费集成开发环境,现在功能已经相当完善,IDE…

3.选择器

选择器: 作用:选择页面上的某一个或者某一类元素 3.1基本选择器:1.标签选择器:选择一类标签 2.类选择器 class:选中所有class属性一致的标签(可以跨标签) .class名称{} 3.id选择器:id全局唯一 #id名称{} 不遵循就近原则:id>class>标签

2.CSS的三种导入方式

1.标签内部 2.head里面 3.css文件 1.链接式:html2.导入式:CSS2.1特有 优先级:行内样式>内部样式=外部样式(后导入的覆盖前导入的--就近原则)

谷歌Pixel 2 刷安卓10系统 APatch获取Root权限

事前准备手机需要解Bootloader锁 , 打开OEM解锁,开启USB调试链接电脑下载Platform-Tools # 地址 https://developer.android.google.cn/tools/releases/platform-tools?hl=zh-cn检查是否需要需要安装Android驱动刷机下载先刷机包 https://developers.google.cn/android/image…

六、汇编实战

打印:hello world 在屏幕上输出字符 mov dl,a ; 将要打印的字符放到dl中 mov ah, 02h ; 设置显示字符的功能号 int 21h ; 调用DOS中断,打印字符在屏幕上输出字符串 mov ah,09h ;设置显示字符串的功能号 int 21H …

可信执行环境

一、隐私计算与可信执行环境 1. 背景:随着云计算和大数据的普及,用户之间需要进行隐私数据的共享与协作,这些数据被上传到云端进行计算和处理。 但是,由于隐私数据交由不可信的第三方存储和管理,用户隐私数据面临被泄露的风险,公民的生命和财产安全乃至国家的安全都受到不…

Foldermove 轻松地把电脑里的软件搬到另一个硬盘,甚至是U盘里

Foldermove 大家好,今天我要介绍一个超实用的小工具,它能让你轻松地把电脑里的软件搬到另一个硬盘,甚至是U盘里,听起来是不是很酷? ●软件简介 软件名:FolderMove 体积:只有201kb,轻得像羽毛。 版本:v3.0 适用系统:Windows ●使用体验 这个小工具,体积小到几乎可以…