Promise高级版 - 通过输出题理解「Promise源码」

1 Promise源码分析

Promise的基本工作原理
Promise构造函数:Promise构造函数接受一个执行器函数作为参数,该函数有两个参数:resolvereject。在构造函数内部,会创建一个Promise实例,并初始化其状态为pending。

状态转换:当执行器函数调用resolve函数时,Promise的状态会变为fulfilled;当调用reject函数时,状态会变为rejected。同时,resolve和reject函数会触发回调函数的执行。

回调函数队列:Promise内部维护一个回调函数队列,用于存储注册的回调函数。当Promise的状态已经变为fulfilledrejected时,回调函数会立即执行;如果Promise的状态还是pending,回调函数会被添加到回调函数队列中,等待状态变化时执行。

then()方法:then()方法用于注册成功回调函数,它接受两个参数:onFulfilledonRejected。当Promise的状态已经变为fulfilled时,会执行onFulfilled回调函数;当状态变为rejected时,会执行onRejected回调函数。then()方法返回一个新的Promise实例,可以实现链式调用。

catch()方法:catch()方法用于注册失败回调函数,它只接受一个参数:onRejected。它实际上是then()方法的一种特殊形式,相当于调用then(undefined, onRejected)

class Promise {constructor(executor) {this.state = 'pending';this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];const resolve = (value) => {if (this.state === 'pending') {this.state = 'fulfilled';this.value = value;this.onResolvedCallbacks.forEach((callback) => callback());}};const reject = (reason) => {if (this.state === 'pending') {this.state = 'rejected';this.reason = reason;this.onRejectedCallbacks.forEach((callback) => callback());}};try {executor(resolve, reject);} catch (error) {reject(error);}}then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {throw reason;};const promise2 = new Promise((resolve, reject) => {// Promise的queueMicrotask方法是用于将一个微任务(microtask)添加到微任务队列中。if (this.state === 'fulfilled') {queueMicrotask(() => {try {const x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}});}if (this.state === 'rejected') {queueMicrotask(() => {try {const x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}});}if (this.state === 'pending') {this.onResolvedCallbacks.push(() => {queueMicrotask(() => {try {const x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}});});this.onRejectedCallbacks.push(() => {queueMicrotask(() => {try {const x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}});});}});return promise2;}catch(onRejected) {return this.then(null, onRejected);}
}/*** resolvePromise是Promise的内部方法之一,用于处理Promise的解析过程。* 它接受三个参数:promise(要解析的Promise实例)、x(解析值)、resolve(用于解析Promise的回调函数)。* resolvePromise的作用是根据解析值x的类型,决定如何处理Promise的解析过程。它遵循Promise/A+规范中的规则,根据不同的情况执行相应的操作。
*/
function resolvePromise(promise, x, resolve, reject) {if (promise === x) {// 如果promise和解析值x是同一个对象,抛出TypeErrorreturn reject(new TypeError('Chaining cycle detected for promise'));}if (x instanceof Promise) {// 如果解析值x是一个Promise实例,等待其状态变为fulfilled或rejected,然后继续解析x.then(resolve, reject);} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {let called = false;try {const then = x.then;if (typeof then === 'function') {then.call(x,(y) => {if (called) return;called = true;resolvePromise(promise, y, resolve, reject);},(r) => {if (called) return;called = true;reject(r);});} else {// 如果解析值x是一个普通对象或函数,将Promise状态设置为fulfilled,并传递解析值xresolve(x);}} catch (error) {if (called) return;called = true;// 如果获取then方法抛出异常,将Promise状态设置为rejected,并传递异常信息reject(error);}} else {// 如果解析值x是一个原始值,将Promise状态设置为fulfilled,并传递解析值xresolve(x);}
}

2 题目背景

这一期还是@洛千陨 珍藏题,带着疑问去看Promise源码:为什么’6’比’hello’先打印?

const p1 = () => (new Promise((resolve, reject) => {console.log(1);resolve(6);let p2 = new Promise((resolve, reject) => {console.log(2);const timeOut1 = setTimeout(() => {console.log(3);resolve(4);}, 0)resolve(5);});p2.then((arg) => {console.log(arg);return 'hello'}).then((value) => {console.log(value)});}));const timeOut2 = setTimeout(() => {console.log(8);const p3 = new Promise(reject => {reject(9);}).then(res => {console.log(res)})}, 0)p1().then((arg) => {console.log(arg);});console.log(10);
输出结果:
1 2 10 5 6 hello 8 9 3

输出结果分析

  • 疑问:为什么’6’比’hello’先打印?
    答: 参照源码,promise的then方法里面会返回一个promise。
    ①p2第一个then里的代码推入微任务队列(第一个),p1.then里的代码推入微任务队列(第二个);
    ②现在来执行第一个微任务打印'5',return 'hello’会把p2第二个then里的代码推入微任务队列(第三个);
    ③执行第二个微任务打印'6'
    ④执行第三个微任务打印'hello'
  • 输出结果的具体分析可以看另一篇博客的题目(1):ECMAScript 6 - 通过输出题理解「Promise」

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

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

相关文章

【GNN2】PyG完成图分类任务,新手入门,保姆级教程

上次讲了如何给节点分类,这次我们来看如何用GNN完成图分类任务,也就是Graph-level的任务。 【GNN 1】PyG实现图神经网络,完成节点分类任务,人话、保姆级教程-CSDN博客 图分类就是以图为单位的分类,举个例子&#xff1…

设备树OF函数操作实验-读取设备节点backlight的整型属性

一. 简介 本文学习使用设备树操作 OF函数,读取设备节点的整型的属性值。 读取设备树文件 imx6ull-14x14-evk.dts 中一个设备节点的信息。这里读取 backlight设备节点的属性值:读取整型的属性。 注意:这里的整型数据都是 32位的数据。而非 …

Open3D 点云等比例缩放(20)

Open3D 点云等比例缩放(20) 一、算法介绍二、算法实现1.代码世人慌慌张张,不过图碎银几两, 偏偏这碎银几两,能解世间万种慌张。 一、算法介绍 实现这样一个功能,沿着中心,按照指定的比例,比如1/2,缩小或者放大点云,保存到新的文件中 二、算法实现 1.代码 import…

小程序基础学习(js混编)

在组件中使用外部js代码实现数据改变 先创建js文件 编写一些组件代码 编写外部js代码 在组件的js中引入外部js 在 app.json中添加路径规则 组件代码 <!--components/my-behavior/my-behavior.wxml--> <view><view>当前计数为{{count}}</view> <v…

操作系统复习 一、二章

操作系统复习 一、二章 文章目录 操作系统复习 一、二章第一章 计算机系统概述处理器中各寄存器的作用指令的执行过程中断存储器层次结构和CacheI/O 通信技术 第二章 操作系统概述大内核微内核大内核微内核 操作系统的定义、目标和功能定义目标和功能 操作系统的发展过程现代操…

Kibana:使用反向地理编码绘制自定义区域地图

Elastic 地图&#xff08;Maps&#xff09;附带预定义区域&#xff0c;可让你通过指标快速可视化区域。 地图还提供了绘制你自己的区域地图的功能。 你可以使用任何您想要的区域数据&#xff0c;只要你的源数据包含相应区域的标识符即可。 但是&#xff0c;当源数据不包含区域…

半监督学习 - 半监督K均值(Semi-Supervised K-Means)

什么是机器学习 半监督K均值&#xff08;Semi-Supervised K-Means&#xff09;是K均值聚类算法的一种扩展&#xff0c;它结合了有标签数据和无标签数据进行聚类。在传统的K均值算法中&#xff0c;所有数据点都是无标签的&#xff0c;而在半监督K均值中&#xff0c;我们允许一部…

RabbitMQ的高可用机制

通过搭建集群保证高可用 RabbitMQ的集群模式&#xff1a; 普通集群&#xff0c;镜像集群&#xff08;开发时用的多&#xff09;&#xff0c;仲裁集群 普通集群&#xff08;标准集群&#xff09;会在各个节点间共享部分数据&#xff08;交换机和队列元信息&#xff09;&#…

最新域名群站开源系统:打造强大网站矩阵,引领SEO优化新潮流!

搭建步骤 第一步&#xff1a;安装PHP和MYSQL服务器环境 对于想要深入了解网站建设的人来说&#xff0c;自己动手安装PHP和MYSQL服务器环境是必不可少的步骤。这将使你能够更好地理解网站的运行机制&#xff0c;同时为后续的网站开发和优化打下坚实基础。 第二步&#xff1a;…

QSpace:Mac上的简洁高效多窗格文件管理器

在Mac用户中&#xff0c;寻找一款能够提升文件管理效率的工具是常见的需求。QSpace&#xff0c;一款专为Mac设计的文件管理器&#xff0c;以其简洁的界面、高效的多窗格布局和丰富的功能&#xff0c;为用户提供了一个全新的文件管理体验。 QSpace&#xff1a;灵活与功能丰富的结…

ImportError: cannot import name ‘Doc‘ from ‘typing_extensions‘

在训练大模型时候出现&#xff1a;ImportError: cannot import name ‘Doc’ from ‘typing_extensions’ 。 问题 原因 安装的typing_extensions版本不正确 解决方法 pip install typing_extensions4.8.0

Python Flask教程

Flask Doc: https://rest-apis-flask.teclado.com/docs/course_intro/what_is_rest_api/Github: https://github.com/tecladocode/rest-apis-flask-python 1. 最简单的应用 最小应用 from flask import Flaskapp Flask(__name__)app.route("/") def hello_world()…

手写webpack的loader

一、概念 帮助webpack将不同类型的文件转换为webpack可识别的模块。 二、Loader执行顺序 分类 pre&#xff1a;前置loadernormal&#xff1a;普通loaderinline&#xff1a;内联loaderpost&#xff1a;后置loader 执行顺序 4类loader的执行顺序为per>normal>inline&…

MATLAB十六进制与十进制互相转换

MATLAB十六进制与十进制互相转换 包含单个数字进行转换和数组进行转换(可用于串口数据解析) 一、单个数字转换 1.十六进制转十进制 % 输入一个十六进制字符串 hexString = 51;% 使用 hex2dec 函数将十六进制转换为十进制 decimalValue = hex2dec(hexString);% 显示结果 d…

极简Oracle 11g Release 2 (11.2.0.1.0)

注意&#xff1a;此法无法安装oracle11g(11.2.0.4)&#xff0c;会报如下错&#xff1a; [FATAL] [INS-10105] The given response file /assets/db_install.rsp is not valid. 一、下载解压ORACLE安装包。 从 oracle 官网 下载所需要的安装包&#xff0c;这里我们以 oracle 11…

二级C语言备考5

一、单选 共40题 &#xff08;共计40分&#xff09; 第1题 &#xff08;1.0分&#xff09; 题号:6100 难度:中 第1章 下列叙述中正确的是 A:程序可以作为算法的一种表达方式 B:算法的有穷性是指算法的规模不能太大 C:算法的复杂度用于衡量算法的控制结…

分类方法之逻辑回归

什么是逻辑回归 逻辑回归是一种用于解决分类问题的统计分析方法。它是一种广义线性模型&#xff0c;主要用于预测一个事件的概率。逻辑回归通过将输入变量和权重进行线性组合&#xff0c;并通过一个特殊的函数&#xff08;称为逻辑函数或Sigmoid函数&#xff09;将结果转化为0…

人声处理用什么软件好 FL Studio 怎么修人声 人声处理软件 人声处理步骤

一、人声处理用什么软件好 现在人声处理软件还是非常多的&#xff0c;有专门的人声处理软件&#xff0c;也有具备人声处理功能的编曲软件。专门人声处理的软件操作比较简单&#xff0c;但是处理后的人声在使用的时候可能还需要进行再处理&#xff0c;这会比较麻烦。具备人声处…

Debian12 安装jenkins 公钥配置

jenkins公钥配置 参考&#xff1a;Debian Jenkins 软件包 这是 Jenkins 的 Debian 软件包存储库&#xff0c;用于自动安装和升级。 要使用此存储库&#xff0c;请先将密钥添加到您的系统&#xff08;对于每周发布行&#xff09;&#xff1a; sudo wget -O /usr/share/keyring…

命令行(无图形界面)登录dlut-lingshui

1 登录原理 利用python的requests库向校园网认证服务器发送认证请求。 2 登录步骤 获取校园网认证界面的用户名和密码。用户名是自己学号&#xff1b;密码由网页加密&#xff0c;需要一台有图形界面的电脑辅助获取&#xff0c;获取方法见下一节。把获取到的用户名和密码填入…