闲谈Promise

news/2024/10/21 18:50:39/

预备知识

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

为什么出现Promise

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

javascript">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:

javascript">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.ppmy.cn/news/1538444.html

相关文章

PAT甲级1007 Maximum Subsequence Sum

题目地址&#xff1a; 编程题 - PAT (Advanced Level) Practice (pintia.cn) 介绍 前缀和 Given a sequence of K integers { N1​, N2​, ..., NK​ }. A continuous subsequence is defined to be { Ni​, Ni1​, ..., Nj​ } where 1≤i≤j≤K. The Maximum Subsequence…

Linux基础与C语言总结

1.常用的Linux命令 常用:cd ls clear man sudo size time strace 文件:touch cat/more/head/tail(查看文件) cp mv rm umask chmod 目录:mkdir rmdir rm -rf cp -frp 网络:ping ifconfig ssh/telent/ftp 其他:ps -aux find grep reboot tar 2.vim文本编辑器的常用操作? …

涂鸦革新WebRTC技术!让IPC监测低延时、高可靠更安全

随着科技的飞速发展&#xff0c;越来越多人开始关注居家安全、食品安全、校园安全等领域&#xff0c;大家对实时监测的需求也在不断升级。想象一下&#xff0c;无论身处何地&#xff0c;只需轻触屏幕&#xff0c;就能实时查看家中、办公室或任何你关心的地方&#xff0c;这不再…

CSS 命名规范及 BEM 在前端开发中的实践

一&#xff1a;CSS命名规范的重要性 1、提高代码可读性 对于开发者自身来说&#xff0c;遵循规范的命名可以让你在日后回顾代码时&#xff0c;快速理解每个样式类的用途。例如&#xff0c;使用 “.header-logo” 这样的命名&#xff0c;一眼就能看出是头部的 logo 元素的样式…

LangChain中使用Prompt01

1.引入提示模板 from langchain.prompts import (SystemMessagePromptTemplate,AIMessagePromptTemplate,HumanMessagePromptTemplate, )2.设置系统提示 system_template_text"你是一位专业的翻译&#xff0c;能够将{input_language}翻译成{output_language}&#xff0c…

开发一个UniApp需要多长时间

开发一个UniApp所需的时间因项目的规模、复杂度、开发团队的经验水平以及开发过程中的需求变更等多种因素而异。因此&#xff0c;很难给出一个确切的时间范围。然而&#xff0c;我们可以从以下几个方面来大致估算开发时间&#xff1a; 项目规划与需求分析&#xff1a; 在项目开…

metahuman如何导入UE5

1.启动 通过EPIC启动UE5(UE5内置有Bridge, 但是UE4是需要单独下在Bridge软件) 2.打开Quixel Bridge 在window(窗口)中打开Quixel Bridge 3.Bridge界面 在弹出的Bridge界面选择模型 需要先下载&#xff0c;然后再导入 4.下载模型 点击需要的模型右上方的绿色箭头下载 5.下…

Java利用ChromeDriver插件网页截图(Wondows版+Linux版)

chromedriver是谷歌浏览器驱动,用来模拟谷歌运行操作的一个工具&#xff0c;此处主要讲解Java后端利用此插件进行网页截图&#xff0c;并且适配Linux部署。 环境准备 Wondows服务器或电脑 本机需安装Chrome谷歌浏览器&#xff0c;根据本机浏览器版本&#xff0c;下载对应的chr…