对 Promise 的理解
Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步 操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调, 它比传统的解决方案回调函数和事件更合理和更强大。
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束 的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一 个对象,从它可以获取异步操作的消息。Promise 提供统一的 API, 各种异步操作都可以用同样的方法进行处理。
- Promise 的实例有三个状态:
-
Pending(进行中)
-
Resolved(已完成)
-
Rejected(已拒绝)
当把一件事情交给 promise 时,它的状态就是 Pending,任务完成了 状态就变成了 Resolved、没有完成失败了就变成了 Rejected。
-
Promise 的实例有两个过程:
pending -> fulfilled: Resolved(已完成)
pending -> rejected: Rejected(已拒绝)注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise 的特点
对象的状态不受外界影响。promise 对象代表一个异步操作,有三种 状态,pending(进行中)、fulfilled(已成功)、rejected(已失 败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他 操作都无法改变这个状态,这也是 promise 这个名字的由来——“承 诺”;
一旦状态改变就不会再变,任何时候都可以得到这个结果。promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled,从 pending 变为 rejected。这时就称为 resolved(已定型)。如果改 变已经发生了,你再对 promise 对象添加回调函数,也会立即得到这 个结果。这与事件(event)完全不同,事件的特点是:如果你错过 了它,再去监听是得不到结果的。
Promise 实现
Promise 实现是通过js class编写,主要包括status、value、error、resolve、reject、then、catch、all、race、allSettled、any等组成。
- 状态
const PROMISE_STATUE_PENDING = "pending"; // 进行中const PROMISE_STATUE_FULFILLED = "fulfilled"; // 已完成const PROMISE_STATUE_REJECTED = "rejected"; // 已拒绝
- resolve 创建一个已解决的Promise对象,将给定的值作为其参数。
resolve = (value) => {if (this.statue === PROMISE_STATUE_PENDING) {queueMicrotask(() => {if (this.statue !== PROMISE_STATUE_PENDING) return;this.statue = PROMISE_STATUE_FULFILLED;this.value = value;this.resFns?.forEach((fn) => {fn(this.value);});});}};
- reject 创建一个已拒绝的Promise对象,将给定的原因作为其参数
reject = (error) => {if (this.statue === PROMISE_STATUE_PENDING) {queueMicrotask(() => {if (this.statue !== PROMISE_STATUE_PENDING) return;this.statue = PROMISE_STATUE_REJECTED;this.error = error;this.errFns.forEach((en) => {en(this.error);});});}};
- then 添加对Promise对象解决或拒绝时的处理程序
- 单个方法调用
constructor(executer) {this.statue = PROMISE_STATUE_PENDING;this.value = void 0;this.error = void 0;this.resFn;this.errFn;const resolve = ((value) => {if (this.status === PROMISE_STATUS_PENDING) {this.status = PROMISE_STATUS_FULFILLEDqueueMicrotask(() => { //queueMicrotask: 主线程执行完毕之后立马执行this.resfn(value)})}})const reject = ((error) => {if (this.status === PROMISE_STATUS_PENDING) {this.status = PROMISE_STATUS_REJECTEDqueueMicrotask(() => {this.errfn(error)})}})executer(this.resolve, this.reject);}then(resFn, errFn) {this.resFn = resFn;this.errFn = errFn;}
- 执行结果
const p1 = new myPromise((resolve, reject) => {resolve(111)reject(333333)
})
p1.then(res => { //最终打印 1111console.log(res);
}, err => {console.log(err);})
- 优化then 方法
官方给与的then 方法是可以进行数组传值和链式调用的,而目前我们写的是不支持。
this.resFns = [] //1.多次调用then 时用数组 保存this.errFns = []// 将then 方法修改为 then(resFn, errFn) {this.resfns.push(resFn);this.errFns.push(errFn);}// resolve修改为resolve = (value) => {if (this.statue === PROMISE_STATUE_PENDING) {queueMicrotask(() => {if (this.statue !== PROMISE_STATUE_PENDING) return;this.statue = PROMISE_STATUE_FULFILLED;this.value = value;this.resFns?.forEach((fn) => {fn(this.value);});});}};// reject 修改为reject = (error) => {if (this.statue === PROMISE_STATUE_PENDING) {queueMicrotask(() => {if (this.statue !== PROMISE_STATUE_PENDING) return;this.statue = PROMISE_STATUE_REJECTED;this.error = error;this.errFns.forEach((en) => {en(this.error);});});}};
优化后then的运行结果
p1.then(res => {console.log("res1:", res)
}, err => {console.log("err1:", err)
})
// 调用then方法多次调用
p1.then(res => {console.log("res2:", res)
}, err => {console.log("err2:", err)
})
运行结果:res2: 111 因为后面的.then 把前面的覆盖掉了 并不会执行res1 所在的代码块
*由此可见 then 方法调用时应该是个数组然后依次调用
下面改造我们的代码then,还需要优化执行resolve 时调用reject
then(resFn, errFn) {const defaultOnRejected = (err) => {throw err;};errFn = errFn || defaultOnRejected;const defaultOnFulFilled = (value) => {return value;};resFn = resFn || defaultOnFulFilled;return new MyPromise((resolve, reject) => {if (this.statue === PROMISE_STATUE_FULFILLED && !!resFn) {try {const value = resFn(this.value);resolve(value);} catch (error) {reject(error);}}if (this.statue === PROMISE_STATUE_REJECTED && !!errFn) {try {resolve(value);} catch (error) {reject(error);}}if (this.statue === PROMISE_STATUE_PENDING) {if (!!resFn) {this.resFns.push(() => {try {const value = resFn(this.value);resolve(value);} catch (error) {reject(error);}});}if (!!errFn) {this.errFns.push(() => {try {const value = errFn(this.error);resolve(value);} catch (error) {reject(error);}});}}});}
然后执行:
const p1 = new myPromise((resolve, reject) => {resolve(111);reject(333333);})p1.then(res => {console.log("res1:", res);}, err => {console.log("err1:", err);})// 调用then方法多次调用p1.then(res => {console.log("res2:", res);}, err => {console.log("err2:", err);})执行结果:res1: 111res2: 111
- catch 添加对Promise对象拒绝时的处理程序
// 添加对Promise对象拒绝时的处理程序。catch(errFn) {return this.then(undefined, errFn);}
- finally 添加对Promise对象解决或拒绝时的最终处理程序,无论Promise对象是否已被解决或拒绝。
finally(fn) {setTimeout(() => {fn();}, 0);}
- all 接收一个可迭代对象(如数组),并返回一个新的Promise对象。当所有Promise对象都已解决时,该Promise对象才将被解决,并返回一个包含所有解决值的数组。
// 通过类型判断当前数组中的方法或者对象是否为Promise 对象const isPromise = function(promise) {return (!!promise &&(typeof promise === "object" || typeof promise === "function") &&typeof promise.then === "function");};/*** 接收一个可迭代对象(如数组),并返回一个新的Promise对象。* 当所有Promise对象都已解决时,该Promise对象才将被解决,并返回一个包含所有解决值的数组。* @param {any[]} iterable* @desc 实际上多个对象同步执行时,就相当于把所有的方法重新进行Promise一次。* 当遍历到最后一个时,resolve 所有结果。* */MyPromise.all = function(iterable) {if (!(iterable instanceof Array)) {return console.log("传入参数必须是一个数组");}return new MyPromise((resolve, reject) => {let len = iterable.length;let count = 0;let results = new Array(len);for (let i = 0; i < len; i++) {let promise = iterable[i];count++;if (isPromise(promise)) {promise.then((res) => {results[i] = res;if (count === len) {resolve(results);}}).catch((err) => {reject(err);});} else if (typeof promise === "function") {results[i] = promise();} else {results[i] = promise;}}// 当数据的所有项都不是promise实例,我们就在这判断多一次,然后resolveif (count === len) {resolve(results);}});};
all 运行示例
(async function() {const res = MyPromise.all([new MyPromise((resolve) => {resolve(1);}),new MyPromise((resolve) => {resolve(2);}),() => {return 123;},88888,]);res.then((res) => {console.log(res);});})();运行结果: [1, 2, 123, 88888]
- race Promise.race(iterable) 传入多个对象,当任何一个执行完成后 resolve 结果
MyPromise.race = function(iterable) {if (!(iterable instanceof Array)) {return console.log("传入参数必须是一个数组");}return new MyPromise((resolve, reject) => {iterable.forEach((p) => {if (isPromise(p)) {p.then((value) => {resolve(value);}).catch((err) => {reject(err);});} else if (typeof p === "function") {resolve(p());} else {resolve(p);}});});
};
race 运行示例
(async function() {const res = MyPromise.race([new MyPromise((resolve) => {resolve(1);}),new MyPromise((resolve) => {resolve(2);}),]);res.then((res) => {console.log(res);});})();运行结果: 1
完整代码
// statusconst PROMISE_STATUE_PENDING = "pending"; // 进行中const PROMISE_STATUE_FULFILLED = "fulfilled"; // 已完成const PROMISE_STATUE_REJECTED = "rejected"; // 已拒绝class MyPromise {constructor(executer) {this.statue = PROMISE_STATUE_PENDING;this.value = void 0;this.error = void 0;this.resFns = [];this.errFns = [];executer(this.resolve, this.reject);}// 创建一个已解决的Promise对象,将给定的值作为其参数。resolve = (value) => {if (this.statue === PROMISE_STATUE_PENDING) {queueMicrotask(() => {if (this.statue !== PROMISE_STATUE_PENDING) return;this.statue = PROMISE_STATUE_FULFILLED;this.value = value;this.resFns?.forEach((fn) => {fn(this.value);});});}};// 创建一个已拒绝的Promise对象,将给定的原因作为其参数。reject = (error) => {if (this.statue === PROMISE_STATUE_PENDING) {queueMicrotask(() => {if (this.statue !== PROMISE_STATUE_PENDING) return;this.statue = PROMISE_STATUE_REJECTED;this.error = error;this.errFns.forEach((en) => {en(this.error);});});}};// 添加对Promise对象解决或拒绝时的处理程序。then(resFn, errFn) {const defaultOnRejected = (err) => {throw err;};errFn = errFn || defaultOnRejected;const defaultOnFulFilled = (value) => {return value;};resFn = resFn || defaultOnFulFilled;return new MyPromise((resolve, reject) => {if (this.statue === PROMISE_STATUE_FULFILLED && !!resFn) {try {const value = resFn(this.value);resolve(value);} catch (error) {reject(error);}}if (this.statue === PROMISE_STATUE_REJECTED && !!errFn) {try {resolve(value);} catch (error) {reject(error);}}if (this.statue === PROMISE_STATUE_PENDING) {if (!!resFn) {this.resFns.push(() => {try {const value = resFn(this.value);resolve(value);} catch (error) {reject(error);}});}if (!!errFn) {this.errFns.push(() => {try {const value = errFn(this.error);resolve(value);} catch (error) {reject(error);}});}}});}// 添加对Promise对象拒绝时的处理程序。catch(errFn) {return this.then(undefined, errFn);}// 添加对Promise对象解决或拒绝时的最终处理程序,无论Promise对象是否已被解决或拒绝。finally(fn) {setTimeout(() => {fn();}, 0);}}const isPromise = function(promise) {return (!!promise &&(typeof promise === "object" || typeof promise === "function") &&typeof promise.then === "function");};/*** 接收一个可迭代对象(如数组),并返回一个新的Promise对象。* 当所有Promise对象都已解决时,该Promise对象才将被解决,并返回一个包含所有解决值的数组。* @param {any[]} iterable*/MyPromise.all = function(iterable) {if (!(iterable instanceof Array)) {return console.log("传入参数必须是一个数组");}return new MyPromise((resolve, reject) => {let len = iterable.length;let count = 0;let results = new Array(len);for (let i = 0; i < len; i++) {let promise = iterable[i];count++;if (isPromise(promise)) {promise.then((res) => {results[i] = res;if (count === len) {resolve(results);}}).catch((err) => {reject(err);});} else if (typeof promise === "function") {results[i] = promise();} else {results[i] = promise;}}// 当数据的所有项都不是promise实例,我们就在这判断多一次,然后resolveif (count === len) {resolve(results);}});};MyPromise.race = function(iterable) {if (!(iterable instanceof Array)) {return console.log("传入参数必须是一个数组");}return new MyPromise((resolve, reject) => {iterable.forEach((p) => {if (isPromise(p)) {p.then((value) => {resolve(value);}).catch((err) => {reject(err);});} else if (typeof p === "function") {resolve(p());} else {resolve(p);}});});};// const p1 = new MyPromise((resolve, reject) => {// console.log("状态pending");// resolve("22222");// reject("3333333");// });// p1.then((res) => {// console.log("res1:", res);// return "第二次的成功回调";// })// .catch((error) => {// console.log("err1:", error);// throw new Error("第二次的失败回调");// })// .finally(() => {// console.log("finally");// });// (async function() {// const res = MyPromise.all([// new MyPromise((resolve) => {// resolve(1);// }),// new MyPromise((resolve) => {// resolve(2);// }),// () => {// return 123;// },// 88888,// ]);// res.then((res) => {// console.log(res);// });// })();// (async function() {// const res = MyPromise.race([// new MyPromise((resolve) => {// resolve(1);// }),// new MyPromise((resolve) => {// resolve(2);// }),// ]);// res.then((res) => {// console.log(res);// });// })();
Promise 的缺点
无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始 还是即将完成)。
总结:
Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。 一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变, 就凝固了,无法再被改变了。
状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在 异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原 型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的 改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的 末尾执行。
注意:在构造 Promise 的时候,构造函数内部的代码是立即执行的。
相关文章:
对 Promise 的理解
Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步 操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调, 它比传统的解决方案回调函数和事件更合理和更强大。 所谓 Promise,简单说就…...
Vuex:Vue.js应用程序的状态管理模式
介绍 在Vue.js应用程序中,随着项目复杂度的增加,组件之间的数据共享和管理变得困难。为了解决这个问题,Vue.js提供了一个名为Vuex的状态管理模式。Vuex可以帮助我们更有效地组织、管理和共享应用程序的状态。 什么是Vuex? Vuex…...

Unity之ShaderGraph 节点介绍 Utility节点
Utility 逻辑All(所有分量都不为零,返回 true)Any(任何分量不为零,返回 true)And(A 和 B 均为 true)Branch(动态分支)Comparison(两个输入值 A 和…...

springboot()—— swagger
零、一张图读懂swagger 懂了,这玩意就是用swagger搞出来的! 就是一个后端开发自测的东西嘛! 一、概念 存在即合理,我们看一下swagger诞生的原因:在前后端分离的架构中,前端新增一个字段,后端就…...

Java课题笔记~ 关联映射
一、MyBatis关联查询 在关系型数据库中,表与表之间存在着3种关联映射关系,分别为一对一、一对多、多对多。 一对一:一个数据表中的一条记录最多可以与另一个数据表中的一条记录相关。列如学生与学号就属于一对一关系。 一对多:主…...

一零六七、JVM梳理
JVM? Java虚拟机,可以理解为Java程序的运行环境,可以执行Java字节码(Java bytecode)并提供了内存管理、垃圾回收、线程管理等功能 java内存区域划分?每块内存中都对应什么? 方法区:类的结构信息、常量池、…...

【CSS】网格布局(简单布局、网格合并、网格嵌套)
文章目录 CSS网格布局(Grid Layout)1. 简单布局2. 网格合并3. 网格嵌套4. 总结 CSS网格布局(Grid Layout) CSS网格布局(Grid Layout)是一种强大且灵活的CSS布局系统,允许开发者以网格形式组织和…...

06 Ubuntu22.04上的miniconda3安装、深度学习常用环境配置
下载脚本 我依然是在清华镜像当中寻找的脚本。这里找脚本真的十分方便,我十分推荐。 wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh 下载十分快速,10秒解决问题 运行miniconda3安装脚本 赋予执…...

【CSS3】CSS3 动画 ② ( 动画序列 | 使用 from 和 to 定义动画序列 | 定义多个动画节点 | 代码示例 )
文章目录 一、动画序列二、代码示例 - 使用 from 和 to 定义动画序列三、代码示例 - 定义多个动画节点 一、动画序列 定义动画时 , 需要设置动画序列 , 下面的 0% 和 100% 设置的是 动画 在 运行到某个 百分比节点时 的 标签元素样式状态 ; keyframes element-move { 0% { tr…...

最优化:建模、算法与理论
最优化:建模、算法与理论 目前在学习 最优化:建模、算法与理论这本书,来此记录一下,顺便做一些笔记,在其中我也会加一些自己的理解,尽量写的不会那么的条条框框(当然最基础的还是要有ÿ…...

拿捏--->打印菱形
文章目录 题目描述算法思路代码示例 题目描述 在屏幕上输出以下图案: 算法思路 代码示例 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() {int n;scanf("%d", &n);//上半部分菱形for (int i 0; i < n; i) //上半部分…...

【SpringBoot笔记】定时任务(cron)
定时任务就是在固定的时间执行某个程序,闹钟的作用。 1.在启动类上添加注解 EnableScheduling 2.创建定时任务类 在这个类里面使用表达式设置什么时候执行 cron 表达式(也叫七子表达式),设置执行规则 package com.Lijibai.s…...

Redis单机,主从,哨兵,集群四大模式
Redis 单机模式 Redis 单机模式是指 Redis 数据库在单个服务器上以独立的、单一的进程运行的模式。在这种模式下,Redis 不涉及数据分片或集群配置,所有的数据和操作都在一个实例中进行。以下是关于 Redis 单机模式的详细介绍: 单一实例&#…...

2023年8月份华为H12-811更新了
801、[单选题]178/832、在系统视图下键入什么命令可以切换到用户视图? A quit B souter C system-view D user-view 试题答案:A 试题解析:在系统视图下键入quit命令退出到用户视图。因此答案选A。 802、[单选题]“网络管理员在三层交换机上创建了V…...

[K8S:命令执行:权限异常:解决篇]:通过更新kubeconfig配置相关信息
文章目录 一:场景复现:1.1:关键信息:1.2:全异常日志输出: 二:解决流程:2.1:更新 kubeconfig:2.1.1:执行命令: 2.2:再次执行…...

帆软设计器报表加载不出折线图的原因
最近在用帆软设计器做可视化图表。偶有遇到因为数据集的字段类型导致加载不出折线,现记录如下。做报表的同行可以参考。 数据库使用了 Oracle 11g。数据集的 SQL 代码片是之前用在另一个单元格报表里面的。页面上有一个率是直接计算得出,我为了方便、就…...
[QCA6174]sdx12平台WiFi QCA6174在驱动加载的时候增加模块参数方法
需求描述 由于开发需要,有时候需要在驱动模块加载的时候增加一个参数,传递给到驱动使用 平台描述 Qualcomm SDX12+QCA6174平台 驱动信息 [ 112.281429] wlan: loading driver v4.0.11.213X [ 112.340262] msm_pcie_enable: PCIe: Assert the reset of endpoint of RC0. …...

Ajax-AJAX请求的不同发送方式
🥔:你一定能成为想要成为的人 发送AJAX请求不同方式 发送AJAX请求不同方式1、jQuery发送AJAX请求2、axios发送AJAX请求(重点)3、fetch发送AJAX请求 发送AJAX请求不同方式 1、jQuery发送AJAX请求 首先需要jquery的js文件…...

简易图书管理系统(面向对象思想)
目录 前言 1.整体思路 2.Book包 2.1Book类 2.2BookList类 3.user包 3.1User类 3.2NormalUser类 3.3AdminUser类 4.operation 4.1IOPeration接口 4.2ExitOperation类 4.3FindOperation类 4.4ShowOperation类 4.5AddOperation类 4.6DelOperation类 4.7BorrowOpera…...

C++ 函数模板与类模板
C最重要的特性之一就是代码重用,为了实现代码重用,代码必须具有通用性。通用代码应不受数据类型的影响,并且可以自动适应数据类型的变化。这种程序设计类型称为参数化程序设计。模板是C支持参数化程序设计的工具,通过它可以实现参…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...