闲谈Promise

预备知识

  • 回调函数:当一个函数作为参数传入另一个函数中,并且它不会立刻执行,当满足一定条件之后,才会执行,这种函数称为回调函数。比如:定时器。
  • 异步任务:与之对应的概念是同步任务,同步任务是在主线程上排队执行,当前面的任务执行完成,才会执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完成不影响下一个任务的指向。简单理解就是同步要按代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。

为什么出现Promise

存在异步任务的代码,不能保证代码按照顺序执行,但在特定的需要下,就是需要在异步中实现顺序执行,这时常使用回调函数去实现,比如下面的代码:

setTimeout(()=>{console.log('找实习');setTimeout(()=>{console.log('面试通过');setTimeout(()=>{console.log('拿offer');setTimeout(()=>{console.log('上班');},2000)},1000)},1000)},2000)

执行结果:

虽然上面的代码能实现我们想要的结果,但一些情形下,需要嵌套更多回调函数时,多层嵌套让代码可读性非常差,后期的维护或者异常处理等都变得特别繁琐,让缩进格式也变得非常麻烦。

回调函数A中嵌套回调函数B,回调函数B中嵌套回调函数C.......这种回调函数多层嵌套的情况被称为回调地狱。回调地狱就是为了实现在异步任务中代码也能顺序执行而出现的一种操作。

为解决回调地狱带来的问题,ES6新增了Promise类,让我们能更优雅的书写复杂的异步任务。

Promise

先用一段代码认识一下Promise:

new Promise((resolve,reject)=>{setTimeout(()=>{resolve('3秒后执行')},3000)
}).then(res=>{console.log(res);})
  • 是一个类,需要使用new Promise来创建实例对象。
  • promise有三种状态:pending(待定),fulfilled(已兑现),rejected(已拒绝)。fulfilled和rejected也可以称为已敲定。promise初始状态是pending,promise的状态转换有两条路:一条pending->fulfilled,一条pending->rejected。状态一旦改变,就不能再发生变化。
  • 调用resolve()方法,pending状态会变成fulfilled状态,并且触发then()中第一个回调方法;调用reject()方法,pending状态会变成rejected状态,并且触发then()中第二个回调方法。
  • promise有三个实例方法,then、catch、finally。其中catch和finally实际都是在调用then方法,只是它们往then中传递的参数不一样。
    catch : then(undefined,reject的回调方法)
    finally : then(A回调,A回调)
  • promise的三个实例方法和六个静态方法都是返回一个新的promise。六个静态方法为:resolve、reject、race、all、allSettled、any。其中race、all、allSettled、any将一个 Promise 可迭代对象作为输入(也可以简单理解成:传递数组作为参数)。

promise的实例方法

下面会对三个实例方法进行重写,对于相同的代码进行了提取:

      // 将方法放到任务队列中的方法function runAsynctask(callback) {if (typeof queueMicrotask === "function") {queueMicrotask(callback);} else if (typeof MutationObserver === "function") {// 创建观察器const obs = new MutationObserver(callback);// 创建元素,添加监听const divNode = document.createElement("div");// 参数1 观察的dom节点// 参数2 观察的选项(childList 观察子节点改变)obs.observe(divNode, { childList: true });// 修改元素内容divNode.innerText = "999999";} else {setTimeout(callback, 0);}}// 对then不同的返回值进行不同操作function judgmentFn(x, p, resolve, reject) {if (x === p) {throw new TypeError("Chaining cycle detected for promise #<Promise>");}if (x instanceof QPromise) {x.then((res) => resolve(res),(err) => reject(err));} else {resolve(x);}}
then方法

​then() 方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。它返回一个 Promise 对象。

手写then方法:

    then(onFulfilled, onRejected) {// 参数判断onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;onRejected = typeof onRejected === "function" ? onRejected : (x) => {throw x;};const p = new QPromise((resolve, reject) => {// 执行方法if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === REJECTED) {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === PENDING) {// 保存回调函数this.#handlers.push({onFulfilled: () => {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},onRejected: () => {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},});}});return p;}
catch方法

catch() :在 promise 被拒绝时调用catch()里的函数;返回一个 Promise 对象。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。

 catch(onRejected) {// 内部调用then方法return this.then(undefined, onRejected);
}
finally方法

finally() :用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数,无论是兑现还是拒绝,都是调用同一个函数。

 finally(onFinally) {return this.then(onFinally, onFinally);}

promise的静态方法

resolve方法

Promise.resolve() :将给定的值转换为一个 Promise。如果这个值本身就是promise,则直接返回这个值(就算这个promise的状态是已拒绝的);其它值就封装成promise,返回的 Promise 将会以该值兑现。

static resolve(value) {// 如果是promise 直接返回if (value instanceof QPromise) {return value;}// 其他值,就直接封装成promise返回return new QPromise((res) => {res(value);});
}
reject方法

reject():返回一个已拒绝的 Promise 对象,拒绝原因 是给定的参数。

static reject(value) {// 不管是什么值(包括 Promise 对象),都重新封装成promise返回return new QPromise((undefined, reject) => {reject(value);});
}
race方法

race():等待第一个敲定;返回一个 Promise,这个返回的 promise 会随着第一个 promise 的敲定而敲定。

static race(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 等待第一个敲定(promise状态的敲定)promises.forEach((p) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {resolve(res);},(err) => {reject(err);});});});
}
allSettled方法

 allSettled():等待全部敲定;当数组中的每一个都已敲定时,返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

   static allSettled(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 等待全部敲定// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = { status: FULFILLED, value: res };// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {results[index] = { status: REJECTED, reason: err };count++;count === promises.length && resolve(results);});});});}
all方法

all():两种情况:

  • 当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是空数组),并返回一个包含所有兑现值的数组。
  • 如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
    static all(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 处理全部兑现// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = res;// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {// 处理第一个拒绝reject(err);});});});}
any方法

any():两种情况:

  • 当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。
  • 当所有输入 Promise 都被拒绝(包括空数组)时,它会以一个包含拒绝原因数组的AggregateError 拒绝。
  static any(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))// 等待结果const errors=[]let count=0promises.forEach((p,index)=>{QPromise.resolve(p).then(res=>{// 第一个兑现resolve(res)},err=>{// 全部拒绝errors[index]=errcount++count===promises.length && reject(new AggregateError(errors,'All promises were rejected'))})})});}

手写Promise

手写promise的完整代码

// 三个状态const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";// 定义QPromise类class QPromise {// 初始状态state = PENDING;// 初始原因result = undefined;// 定义实例属性#handlers = [];// 构造函数constructor(fn) {const resolve = (result) => {// 状态不可逆if (this.state === PENDING) {// 修改状态this.state = FULFILLED;this.result = result;// 调用成功回调this.#handlers.forEach(({ onFulfilled }) => {onFulfilled(this.result);});}};const reject = (result) => {// 状态不可逆if (this.state === PENDING) {// 修改状态this.state = REJECTED;this.result = result;// 调用失败回调this.#handlers.forEach(({ onRejected }) => {onRejected(this.result);});}};try {// 执行fn(resolve, reject);} catch (error) {reject(error);}}// then :将方法放到微任务队列中then(onFulfilled, onRejected) {// 参数判断onFulfilled =typeof onFulfilled === "function" ? onFulfilled : (x) => x;onRejected =typeof onRejected === "function"? onRejected: (x) => {throw x;};const p = new QPromise((resolve, reject) => {// 执行方法if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === REJECTED) {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === PENDING) {// 保存回调函数this.#handlers.push({onFulfilled: () => {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},onRejected: () => {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},});}});return p;}// 实例方法catch(onRejected) {// 内部调用then方法return this.then(undefined, onRejected);}// 实例方法finally(onFinally) {return this.then(onFinally, onFinally);}// 静态方法static resolve(value) {// 如果是promise 直接返回if (value instanceof QPromise) {return value;}// 其他值,就直接封装成promise返回return new QPromise((res) => {res(value);});}static reject(value) {// 不管是什么值(包括 Promise 对象),都重新封装成promise返回return new QPromise((undefined, reject) => {reject(value);});}// 等待第一个敲定static race(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 等待第一个敲定(promise状态的敲定)promises.forEach((p) => {QPromise.resolve(p).then((res) => {resolve(res);},(err) => {reject(err);});});});}// 全部状态为成功,则返回一个数组// 若有一个失败,则处理失败static all(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 处理全部兑现// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {QPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = res;// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {// 处理第一个拒绝reject(err);});});});}// 等待全部状态敲定,不管是失败还是成功状态,都放在一个数组中返回static allSettled(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 等待全部敲定// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {QPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = { status: FULFILLED, value: res };// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {results[index] = { status: REJECTED, reason: err };count++;count === promises.length && resolve(results);});});});}// 返回第一个状态兑现(成功)的结果// 全部拒绝时,返回拒绝的结果数组static any(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))// 等待结果const errors=[]let count=0promises.forEach((p,index)=>{QPromise.resolve(p).then(res=>{// 第一个兑现resolve(res)},err=>{// 全部拒绝errors[index]=errcount++count===promises.length && reject(new AggregateError(errors,'All promises were rejected'))})})});}}// 异步函数function runAsynctask(callback) {if (typeof queueMicrotask === "function") {queueMicrotask(callback);} else if (typeof MutationObserver === "function") {// 创建观察器const obs = new MutationObserver(callback);// 创建元素,添加监听const divNode = document.createElement("div");// 参数1 观察的dom节点// 参数2 观察的选项(childList 观察子节点改变)obs.observe(divNode, { childList: true });// 修改元素内容divNode.innerText = "999999";} else {setTimeout(callback, 0);}}// 对then不同的返回值进行不同操作function judgmentFn(x, p, resolve, reject) {if (x === p) {throw new TypeError("Chaining cycle detected for promise #<Promise>");}if (x instanceof QPromise) {x.then((res) => resolve(res),(err) => reject(err));} else {resolve(x);}}

如果本文对你有帮助,希望能得到你的点赞或收藏或关注,这是对我最好的鼓励;

如你有问题或疑惑,欢迎在评论区写下,必将努力解答;

如本文有误区,希望你不吝赐教,让我们共勉!

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

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

相关文章

【Axure原型分享】标签管理列表

今天和大家分享通过标签管理列表的原型模板&#xff0c;包括增删改查搜索筛选排序分页翻页等效果&#xff0c;这个模板是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;初始数据我们只要在中继器表格里填写即可&#xff0c;具体效果可以观看下方视频或者打开预览地址…

Cesium.js(SuperMap iClient3D for Cesium)进行三维场景展示和图层动画

1&#xff09;&#xff1a;参考API文档&#xff1a;SuperMap iClient3D for Cesium 开发指南 2&#xff09;&#xff1a;官网示例&#xff1a;support.supermap.com.cn:8090/webgl/Cesium/examples/webgl/examples.html#layer 3&#xff09;&#xff1a;SuperMap iServer&…

电鳗带来灵感,防潮电源诞生,全打印技术的魅力

大家好&#xff01;今天来了解一项受电鳗启发的防潮完全可打印电源的研究——《A moisture-enabled fully printable power source inspired by electric eels》发表于《PNAS》。随着可穿戴电子设备的发展&#xff0c;对安全、一次性且具成本效益的电源需求大增。传统电池存在不…

LibreOffice SDK是LibreOffice软件的开发工具包

LibreOffice SDK是LibreOffice软件的开发工具包&#xff0c;它提供了一系列工具和库&#xff0c;使得开发者可以基于LibreOffice进行扩展或开发新的应用程序。以下是对LibreOffice SDK的详细介绍&#xff1a; 一、下载与安装 下载地址&#xff1a; 可以在LibreOffice的官方网站…

利用 Llama 3.1模型 + Dify开源LLM应用开发平台,在你的Windows环境中搭建一套AI工作流

文章目录 1. 什么是Ollama&#xff1f;2. 什么是Dify&#xff1f;3. 下载Ollama4. 安装Ollama5. Ollama Model library模型库6. 本地部署Llama 3.1模型7. 安装Docker Desktop8. 使用Docker-Compose部署Dify9. 注册Dify账号10. 集成本地部署的 Llama 3.1模型11. 集成智谱AI大模型…

RUM性能优化之图片加载

作者&#xff1a;三石 在现代Web开发中&#xff0c;图片作为内容表达的核心元素&#xff0c;其加载效率直接影响到页面的整体性能和用户体验。随着高清大图和动态图像的普及&#xff0c;优化图片加载变得尤为重要。RUM作为一种主动监测技术&#xff0c;能够帮助开发者从真实用户…

【CubeMLP】核心方法解读

abstract&#xff1a; 多模态情绪分析和抑郁估计是利用多模态数据预测人类心理状态的两个重要研究课题。以前的研究主要集中在开发有效的融合策略&#xff0c;以交换和整合来自不同模式的心智相关信息。一些基于mlp的技术最近在各种计算机视觉任务中取得了相当大的成功。受此启…

如何解决 Vim 中的 “E212: Can‘t open file for writing“ 错误:从编辑到权限管理(sudo)

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

C语言 | Leetcode C语言题解之第468题验证IP地址

题目&#xff1a; 题解&#xff1a; char * validIPAddress(char * queryIP) {int len strlen(queryIP);if (strchr(queryIP, .)) {// IPv4int last -1;for (int i 0; i < 4; i) {int cur -1;if (i 3) {cur len;} else {char * p strchr(queryIP last 1, .);if (p…

在Leaflet中使用divIcon进行自定义标绘实战

前言 之前在一些地理信息和旅游博主的信息介绍中&#xff0c;对于一些景点的信息时空分布。总是被他们的地图制作所深深吸引。与常规的地图文字标绘不同的是&#xff0c;在传统的地图标绘中&#xff0c;我们习惯于将文字信息直接标注到对应的位置点旁边。当然&#xff0c;这样其…

电脑快速切换IP地址命令是什么?详解与实践

有时&#xff0c;出于安全考虑或测试需要&#xff0c;我们可能需要快速切换电脑的IP地址。虽然这一过程在初学者看来可能略显复杂&#xff0c;但通过简单的命令和步骤&#xff0c; 即使是普通用户也能轻松实现。本文将详细介绍在Windows系统中快速切换IP地址的几种方法&#xf…

基于Jenkins+K8S构建DevOps自动化运维管理平台

目录 1.k8s助力DevOps在企业落地实践 1.1 传统方式部署项目为什么发布慢&#xff0c;效率低&#xff1f; 1.2 上线一个功能&#xff0c;有多少时间被浪费了&#xff1f; 1.3 如何解决发布慢&#xff0c;效率低的问题呢&#xff1f; 1.5 什么是DevOps&#xff1f; 1.5.1 敏…

潜水打捞系统助力,破解汽车打捞难题

随着人类活动的不断扩展&#xff0c;汽车落水事故频发&#xff0c;成为救援工作中的一大难题。汽车因其重量和结构特性&#xff0c;一旦沉入水体&#xff0c;打捞工作将面临巨大挑战。传统的打捞方法往往效率低下&#xff0c;且在操作过程中可能会对汽车造成进一步的损害&#…

Windows 下纯手工打造 QT 开发环境

用过 QtCreator 和 VS QT 插件&#xff0c;都觉得不是很理想。所以有了这个想法。 手工打造的 QT 的开发环境&#xff0c;是不需要安装上面两个程序的。 1、下载 vcpkg&#xff0c;编译 QT6 下载地址&#xff1a;https://github.com/microsoft/vcpkg.git 进入到 …

comfyui提示没有anything everywhere节点

错误信息说明 在使用comfyUI导入工作流的时候&#xff0c;提示没有Anything Everywhere节点 解决办法 进入到ComfyUI的custom_nodes目录下&#xff0c;执行以下命令 git clone https://github.com/chrisgoringe/cg-use-everywhere.git重启ComfyUI即可

顶会论文复现:PROVING TEST SET CONTAMINATION IN BLACK BOX LANGUAGE MODELS

文章目录 1 资料2 我的总结3 复现源码首先你需要有gpt的api接口安装&#xff1a;数据集执行指令源码 4 结果 1 资料 我复现的源码:https://github.com/Whiffe/test_set_contamination 官网源码&#xff1a;https://github.com/tatsu-lab/test_set_contamination 论文&#x…

禁用微软的windos安全中心

目录 一、为什么禁用 二、WDControl_1.5.0程序禁用windows安全中心 步骤1--- 步骤2--- 三、禁用widows安全中心成功 一、为什么禁用 描述&#xff1a;下载第三方软件常常会收到病毒防护秒杀&#xff0c; 第1---直接无法下载 第2---提前下载在U盘解压会被干掉程序文件 …

Spark第一天

MapReduce过程复习 Spark由五部分组成 RDD五大特征 1、 Spark -- 代替MapReduce <<<<< scala是单机的&#xff0c;spark是分布式的。>>>>> 开源的分布式计算引擎 可以快速做计算 -- 因为可以利用内存来做一些计算 (1) 分为5个库(模块) : 1、…

【hot100-java】排序链表

链表题。 使用归并排序法。 一图解决。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; thi…

网络安全之XXE攻击

0x01 什么是 XXE 个人认为&#xff0c;XXE 可以归结为一句话&#xff1a;构造恶意 DTD 介绍 XXE 之前&#xff0c;我先来说一下普通的 XML 注入&#xff0c;这个的利用面比较狭窄&#xff0c;如果有的话应该也是逻辑漏洞。 既然能插入 XML 代码&#xff0c;那我们肯定不能善罢…