Promise 详解(原理篇)

目录

什么是 Promise

实现一个 Promise

Promise 的声明

解决基本状态

添加 then 方法

解决异步实现

解决链式调用

完成 resolvePromise 函数

解决其他问题

添加 catch 方法

添加 finally 方法

添加 resolve、reject、race、all 等方法

如何验证我们的 Promise 是否正确?


什么是 Promise

Promise 是一种异步编程的解决方案。在异步操作中,callback 会导致回调地狱的问题,Promise 解决了这个问题。

一个 Promise对象有以下三种状态:

  • pending:初始状态,既不是成功,也不是失败状态。
  • fulfilled(resolved):意味着操作成功完成。
  • rejected:意味着操作失败。

Promise对象内部运行的一个变化, 变化如下:

  1. 当 new Promise() 被实例化后,即表示 Promise 进入 pending 初始化状态,准备就绪,等待运行。
  2. 一旦 Promise 实例运行成功或者失败之后,实例状态就会变为 fulfilled 或者 rejected,此时状态就无法变更。

在使用 Promise 时,通常会调用其 then() 方法来处理异步操作的结果,或者调用 catch() 方法来处理出错信息。同时,Promise 还提供了一些静态方法,如 Promise.resolve()、Promise.reject() 等用于快速创建一个 Promise 实例。

实现一个 Promise

(下文所指的规定是指 Promise A+规范

Promise 的声明

首先呢,promise 肯定是一个类,我们就用 class 来声明。

  • 由于 new Promise((resolve, reject)=>{}) ,所以传入一个参数(函数 executor),传入就执行
  • executor 里面有两个参数,一个叫 resolve(成功),一个叫 reject(失败)
  • 由于 resolve 和 reject 可执行,所以都是函数,我们用 let 声明
class myPromise{// 构造器constructor(executor){// 成功let resolve = () => { };// 失败let reject = () => { };// 立即执行executor(resolve, reject);}
}
解决基本状态

Promise 有规定:

  • Promise 存在三个状态(state)pending、fulfilled、rejected
  • pending(等待态)为初始态,并可以转化为 fulfilled(成功态)和 rejected(失败态)
  • 成功时,不可转为其他状态,且必须有一个不可改变的值(value)
  • 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
  • new Promise((resolve, reject)=>{resolve(value)}) resolve 为成功,接收参数 value,状态改变为 fulfilled,不可再次改变。
  • new Promise((resolve, reject)=>{reject(reason)}) reject 为失败,接收参数 reason,状态改变为 rejected,不可再次改变。
  • 若是 executor 函数报错 直接执行 reject();

于是乎,我们获得以下代码

class myPromise {constructor(executor) {// 初始化 state 为等待态this.state = 'pending'// 成功的值this.value = undefined// 失败的原因this.reason = undefinedlet resolve = value => {// state 改变 resolve 调用就会失败if (this.state === 'pending') {// resolve 调用后 state 转化为成功态this.state = 'fulfilled'}// 储存成功的值this.value = value}let reject = reason => {// state 改变 reject 调用就会失败if (this.state === 'pending') {// reject 调用后 state 转化为失败态this.state = 'rejected'}// 储存失败的原因this.reason = reason}// 立即执行 executor,如果 executor 执行报错,直接执行 rejecttry {executor(resolve, reject)} catch(err) {reject(err)}}
}
添加 then 方法

Promise 有一个叫做 then 的方法,里面有两个参数:onFulfilled 和 onRejected,成功有成功的值,失败有失败的原因

  • 当状态 state 为 fulfilled,则执行 onFulfilled,传入 this.value。
  • 当状态 state 为 rejected,则执行 onRejected,传入 this.reason
  • onFulfilled 和 onRejected 如果是函数,则必须分别在 fulfilled,rejected 后被调用,value 或reason 依次作为他们的第一个参数
class myPromise {constructor(executor){...}// then 方法有两个参数 onFulfilled 和 onRejectedthen(onFulfilled, onRejected) {// 状态为 fulfilled,执行 onFulfilled,传入成功的值if (this.state === 'fulfilled') {onFulfilled(this.value)}// 状态为 rejected,执行 onRejected,传入失败的原因if (this.state === 'rejected') {onRejected(this.reason)}}
}
解决异步实现

现在基本可以实现简单的同步代码,但是当 resolve 在 setTomeout 内执行,调用 then 时 state 还是 pending 等待态,我们就需要在 then 调用的时候,将成功和失败存到各自的数组,一旦 reject 或者 resolve,就调用它们

类似于发布订阅,先将 then 里面的两个函数储存起来,由于一个 promise 可以有多个 then,所以存在同一个数组内。

// 多个 then 的情况
let p = new Promise();
p.then();
p.then();

成功或者失败时,forEach 调用它们

class myPromise {constructor(executor) {this.state = 'pending'this.value = undefinedthis.reason = undefined// 成功存放的数组this.onResolvedCallbacks = []// 失败存放的数组this.onRejectedCallbacks = []let resolve = value => {if (this.state === 'pending') {this.state = 'fulfilled'}this.value = value// 一旦 resolve 执行,调用成功数组的函数this.onResolvedCallbacks.forEach(fn => fn());}let reject = reason => {if (this.state === 'pending') {this.state = 'rejected'}this.reason = reason// 一旦 reject 执行,调用失败数组的函数this.onRejectedCallbacks.forEach(fn => fn())}try {executor(resolve, reject)} catch(err) {reject(err)}}then(onFulfilled, onRejected) {if (this.state === 'fulfilled') {onFulfilled(this.value)}if (this.state === 'rejected') {onRejected(this.reason)}// 状态为 pending 时if (this.state === 'pending') {// onFulfilled 传入到成功数组this.onResolvedCallbacks.push(() => onFulfilled(this.value))// onRejected 传入到失败数组this.onRejectedCallbacks.push(() => onRejected(this.reason))}}
}
解决链式调用

我门常常用到 new Promise().then().then(),这就是链式调用,用来解决回调地狱

  1. 为了达成链式,我们默认在第一个 then 里返回一个 promise。规定了一种方法,就是在then 里面返回一个新的 promise 称为 promise2
    let promise2 = new Promise((resolve, reject)=>{})
    1. 将这个 promise2 返回的值传递到下一个 then 中
    2. 如果返回一个普通的值,则将普通的值传递给下一个 then 中
  2. 当我们在第一个 then 中 return 了一个参数(参数未知,需判断)。这个 return 出来的新的promise 就是 onFulfilled() 或 onRejected() 的值

规定 onFulfilled() 或 onRejected() 的值,即第一个 then 返回的值,叫做 x,判断 x 的函数叫做 resolvePromise

class myPromise {constructor(executor){...}then(onFulfilled, onRejected) {   // 声明返回的 promise2let promise2 = new myPromise((resolve, reject) => {if (this.state === 'fulfilled') {let x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)}if (this.state === 'rejected') {let x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)}if (this.state === 'pending') {this.onResolvedCallbacks.push(() => {let x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)})this.onRejectedCallbacks.push(() => {let x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)})}})// 返回 Promise,完成链式return promise2}
}
完成 resolvePromise 函数

规定了一段代码,让不同的 promise 代码互相套用,叫做 resolvePromise

  • 如果 x === promise2,会造成循环引用,自己等待自己完成,则报“循环引用”错误
    let p = new Promise(resolve => {resolve(0);
    });
    var p2 = p.then(data => {// 循环引用,自己等待自己完成,一辈子完不成return p2;
    })
    
  • x 不能是 null
  • x 是普通值,直接 resolve(x)
  • x 是对象或者函数(默认 promise), let then = x.then
  • 如果取 then 报错,则走 reject()
  • 如果 then 是个函数,则用 call 执行 then,第一个参数是 x,后面是成功的回调和失败的回调
  • 如果成功的回调还是 pormise,就递归继续解析
  • 因为成功和失败只能调用一个,所以设定一个 called 来防止多次调用
// 处理 x 的值,如果是普通值直接返回,如果是 promise 则返回 x.then 执行的结果
function resolvePromise(promise2, x, resolve, reject) {// 如果 new 出来的 Promise2 和 x 是同一个,循环引用报错if (promise2 === x) return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));// 先判断是不是对象或者函数if (x !== null && (typeof x === 'object' || typeof x === 'function')) {// 调用了成功就不能失败,调用了失败就不能成功,不能多次调用成功或者失败let called// 内部可能抛出错误try {// 声明 then = x 的 then 方法let then = x.then// 如果 then 是函数,就默认是 promise 了if (typeof then === 'function') {this.call(x, res => {// 成功和失败只能调用一个if (called) returncalled = true// res 可能是一个 promise,递归调用 resolvePromise,直到解析出的值是普通值resolvePromise(promise2, res, resolve, reject)}, err => {// 成功和失败只能调用一个if (called) returncalled = true// 直接调用 reject 作为失败的结果,并向下传递reject(err)})} else {// 如果 then 不是函数,就说明是个普通值,直接返回 xresolve(x)}} catch(err) {// 也属于失败,成功和失败只能调用一个if (called) returncalled = true// 直接调用 reject 作为失败的结果,并向下传递reject(err)}} else {// x 是普通值,直接返回resolve(x)}
}
解决其他问题
  • 规定 onFulfilled 和 onRejected 都是可选参数,如果他们不是函数,必须被忽略
  • onFulfilled 返回一个普通的值,成功时直接等于 value => value
  • onRejected 返回一个普通的值,失败时如果直接等于 value => value,则会跑到下一个 then 中的 onFulfilled 中,所以直接扔出一个错误 reason => throw err
  • 规定 onFulfilled 或 onRejected 不能同步被调用,必须异步调用。我们就用 setTimeout 解决异步问题
  • 如果 onFulfilled 或 onRejected 报错,则直接返回 reject()
class myPromise {constructor(executor) {this.state = 'pending'this.value = undefinedthis.reason = undefinedthis.onResolvedCallbacks = []this.onRejectedCallbacks = []let resolve = value => {if (this.state === 'pending') {this.state = 'fulfilled'}this.value = valuethis.onResolvedCallbacks.forEach(fn => fn());}let reject = reason => {if (this.state === 'pending') {this.state = 'rejected'}this.reason = reasonthis.onRejectedCallbacks.forEach(fn => fn())}try {executor(resolve, reject)} catch(err) {reject(err)}}then(onFulfilled, onRejected) {// onFulfilled 如果不是函数,忽略 onFulfilled, 直接返回 valueonFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val// onRejected 如果不是函数,忽略 onRejected, 让它等于一个函数,并且在函数内继续将 err 向下抛出onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}let promise2 = new Promise((resolve, reject) => {if (this.state === 'fulfilled') {// 异步setTimeout(() => {try {let x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch(err) {reject(err)}}, 0)}if (this.state === 'rejected') {// 异步setTimeout(() => {try {let x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch(err) {reject(err)}}, 0)}if (this.state === 'pending') {this.onResolvedCallbacks.push(() => {// 异步setTimeout(() => {try {let x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch(err) {reject(err)}}, 0)})this.onRejectedCallbacks.push(() => {// 异步setTimeout(() => {try {let x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch(err) {reject(err)}}, 0)})}})return promise2}
}
添加 catch 方法
class myPromise {constructor(executor) {...}then(onFulfilled, onRejected) {...}// Promise 中的 catch 指代的就是 then 没有成功回调的一个别名而已catch(errCallback) {return this.then(null, errCallback);}
}
添加 finally 方法
// 无论如何都会执行,把上一个 then 的结果向下传递
// 如果 finally 中返回了一个 Promise 会等待这个 Promise 执行完成后继续执行
myPromise.prototype.finally = function(callback) {return this.then(val => {return Promise.resolve(callback()).then(() => val);}, (err) => {return Promise.resolve(callback()).then(() => { throw err; });});
};
添加 resolve、reject、race、all 等方法
// Promise.resolve 会等待里面的 Promise 执行成功
Promise.resolve = val => {return new Promise((resolve) => {resolve(val);});
};// Promise.reject 不会等待参数中的 Promise 执行完毕
Promise.reject = () => {return new Promise((_, reject) => {reject(val);});
};// all 方法表示等待所有的 Promise 全部成功后才会执行回调
// 如果有一个 Promise 失败则 Promise 就失败了
Promise.all = promises => {return new Promise((resolve, reject) => {// 存放结果const res = [];// 计数,当count 等于 length的时候就resolvelet count = 0;const resolveRes = (index, data) => {// 将执行结果缓存在res中res[index] = data;// 所有子项执行完毕之后,执行resolve 抛出所有的执行结果if (++count === promises.length) {resolve(res);}};// 循环遍历每一个参数的每一项for(let i = 0; i < promises.length; i++) {const current = promises[i];// 如果当前项是Promise,则返回 then 的结果if (isPromise(current)) {current.then((data) => {resolveRes(i, data);}, (err) => {reject(err);});} else {resolveRes(i, current);}}});
}// race谁是第一个完成的,就用他的结果
// 如果失败这个 Promise 就失败,如果第一个是成功就是成功
Promise.race = (promises) => {return new Promise((resolve, reject) => {for(let i = 0; i < promises.length; i++) {let current = promises[i];// 如果是一个 promise 继续执行 thenif (isPromise(current)) {current.then(resolve, reject);} else {// 是普通值则直接 resolve 返回,并终止循环resolve(current);break;}}});
}
如何验证我们的 Promise 是否正确?
  1. 先在后面加上下述代码
    // 目前是通过他测试 他会测试一个对象
    // 语法糖
    Promise.defer = Promise.deferred = function () {let dfd = {}dfd.promise = new Promise((resolve,reject)=>{dfd.resolve = resolve;dfd.reject = reject;});return dfd;
    }
    module.exports = Promise;
    //npm install promises-aplus-tests 用来测试自己的 promise 符不符合 promises A+ 规范
  2. npm 有一个 promises-aplus-tests 插件
    // Windows 全局安装
    npm i promises-aplus-tests -g
    // Mac 全局安装
    sudo npm i promises-aplus-tests -g
  3. 命令行 promises-aplus-tests [js文件名],即可验证

注意⚠️:本文 Promise 已满足基本使用,但还是存在一些问题待改进。。。

参考

史上最最最详细的手写Promise教程

Promise 实现原理

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

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

相关文章

分布式搜索之Elasticsearch入门

Elasticsearch 是什么 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎&#xff0c;能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心&#xff0c;它集中存储您的数据&#xff0c;帮助您发现意料之中以及意料之外的情况。 Elastic Stack 又是什么呢&a…

企业须善用数字化杠杆经营获取数字化时代红利

​在当今数字化时代&#xff0c;企业面临着新机遇和新挑战。数字化技术的迅速发展正在重塑商业格局&#xff0c;企业若能善用数字化杠杆经营&#xff0c;将能够在激烈的市场竞争中脱颖而出&#xff0c;获取丰厚的时代红利。 数字化杠杆的内涵 数字化杠杆是指企业借助数字化技术…

SAPUI5基础知识16 - 深入理解MVC架构

1. 背景 经过一系列的练习&#xff0c;相信大家对于SAPUI5的应用程序已经有了直观的认识&#xff0c;我们在练习中介绍了视图、控制器、模型的概念和用法。在本篇博客中&#xff0c;让我们回顾总结下这些知识点&#xff0c;更深入地理解SAPUI5的MVC架构。 首先&#xff0c;让…

Android 性能优化之卡顿优化

文章目录 Android 性能优化之卡顿优化卡顿检测TraceView配置缺点 StricktMode配置违规代码 BlockCanary配置问题代码缺点 ANRANR原因ANRWatchDog监测解决方案 Android 性能优化之卡顿优化 卡顿检测 TraceViewStricktModelBlockCanary TraceView 配置 Debug.startMethodTra…

XMl基本操作

引言 使⽤Mybatis的注解⽅式&#xff0c;主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能&#xff0c;建议使⽤XML来配置映射语句&#xff0c;也就是将SQL语句写在XML配置⽂件中. 之前&#xff0c;我们学习了&#xff0c;用注解的方式来实现MyBatis 接下来我们…

【STM32】按键控制LED光敏传感器控制蜂鸣器(江科大)

一、按键控制LED LED.c #include "stm32f10x.h" // Device header/*** 函 数&#xff1a;LED初始化* 参 数&#xff1a;无* 返 回 值&#xff1a;无*/ void LED_Init(void) {/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENAB…

数据结构(稀疏数组)

简介 稀疏数组是一种数据结构&#xff0c;用于有效地存储和处理那些大多数元素都是零或者重复值的数组。在稀疏数组中&#xff0c;只有非零或非重复的元素会被存储&#xff0c;从而节省内存空间。 案例引入 假如想把下面这张表存入文件&#xff0c;我们会怎么做&#xff1f;…

超简单安装指定版本的clickhouse

超简单安装指定版本的clickhouse 命令执行shell脚本 idea连接 命令执行 参考官网 # 下载脚本 wget https://raw.githubusercontent.com/183461750/doc-record/d988dced891d70b23c153a3bbfecee67902a3757/middleware/data/clickhouse/clickhouse-install.sh # 执行安装脚本(中…

记录些Spring+题集(12)

11种API性能优化方法 一、索引优化 接口性能优化时&#xff0c;大家第一个想到的通常是&#xff1a;优化索引&#xff0c;优化索引的成本是最小的。 你可以通过查看线上日志或监控报告&#xff0c;发现某个接口使用的某条SQL语句耗时较长。 这条SQL语句是否已经加了索引&…

PHP Program to print pyramid pattern (打印金字塔图案的程序)

编写程序打印由星星组成的金字塔图案 例子 &#xff1a; 输入&#xff1a;n 6输出&#xff1a; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 我们强烈建…

基于术语词典干预的机器翻译挑战赛笔记Task2 #Datawhale AI 夏令营

上回&#xff1a; 基于术语词典干预的机器翻译挑战赛笔记Task1 跑通baseline Datawhale AI 夏令营-CSDN博客文章浏览阅读718次&#xff0c;点赞11次&#xff0c;收藏8次。基于术语词典干预的机器翻译挑战赛笔记Task1 跑通baselinehttps://blog.csdn.net/qq_23311271/article/d…

Linux网络:应用层协议HTTP(一)

一、什么是HTTP协议 虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。 在互联网世界中&#xff0c;HTTP&#xff08;HyperText Transfer Protocol&…

【考研数学】线代满分经验分享+备考复盘

我一战二战复习都听了李永乐的线代课&#xff0c;二战的时候只听了一遍强化&#xff0c;个人感觉没有很乱&#xff0c;永乐大帝的课逻辑还是很清晰的。 以下是我听向量这一章后根据听课内容和讲义例题总结的部分思维导图&#xff0c;永乐大帝讲课的时候也会特意点到线代前后联…

基于百度地图API实现地图位置选取的移动端页面开发教程 (JQuery+Html+JavaScript+CSS)

本文详细讲解了如何使用百度地图API实现移动端页面中的地图位置选取功能。文章首先介绍了百度地图API的2.0和3.0版本功能&#xff0c;并重点采用3.0 API。接着&#xff0c;逐步展示了如何构建基本的地图页面&#xff0c;如何通过点击地图获取经纬度和地理信息&#xff0c;以及如…

uniapp vue3 上传视频组件封装

首先创建一个 components 文件在里面进行组件的创建 下面是 vvideo组件的封装 也就是图片上传组件 只是我的命名是随便起的 <template><!-- 上传视频 --><view class"up-page"><!--视频--><view class"show-box" v-for"…

【Android面试八股文】荣耀面试算法题:输入一个N阶方阵(0<N<10),输出此方阵顺时针旋转M(0<=M<=10000)次后的方阵

文章目录 1. 算法题:输入一个N阶方阵(0<N<10),输出此方阵顺时针旋转M(0<=M<=10000)次后的方阵1.1 题目描述1.2 算法实现1.2.1 步骤说明:1.2.2 算法实现1.2.3 代码实现:1.2.4 程序说明:1.2.5 示例详细讲解如何将一个矩阵顺时针旋转90度1. 算法题:输入一个N阶方…

达梦数据库DM8-索引篇

目录 一、前景二、名词三、语法1、命令方式创建索引1.1 创建索引空间1.2.1 创建普通索引并指定索引数据空间1.2.2 另一种没验证&#xff0c;官方写法1.3 复合索引1.4 唯一索引1.5 位图索引1.6 函数索引 2、创建表时候创建索引3、可视化方式创建索引3.1 打开DM管理工具3.2 找到要…

生成式人工智能落地校园与课堂的15个场景

生成式人工智能正在重塑教育行业&#xff0c;为传统教学模式带来了革命性的变化。随着AI的不断演进&#xff0c;更多令人兴奋的应用场景将逐一显现&#xff0c;为学生提供更加丰富和多元的学习体验。 尽管AI在教学中的应用越来越广泛&#xff0c;但教师们也不必担心会被完全替代…

[电机控制]-三相鼠笼电机simulink建模

三相鼠笼电机simulink建模 1 方程 电机方程&#xff1a; d i s α d t K 1 i s α K 2 ϕ r α K 3 ω r ϕ r β K 4 v s α \frac{di_{s\alpha}}{dt}K_{1}i_{s\alpha}K_{2}\phi_{r\alpha}K_{3}\omega_{r}\phi_{r\beta}K_{4}v_{s\alpha} dtdisα​​K1​isα​K2​ϕrα…

VSCODE 下 openocd Jlink 的配置笔记

title: VSCODE 下 openocd Jlink 的配置笔记 tags: STM32HalCubemax 文章目录 内容VSCODE 下 openocd Jlink 的配置笔记安装完成后修改jlink的配置文件然后修改你的下载器为jlink烧录你的项目绝对会出现下面的问题那么打开下载的第一个软件 &#xff08;点到这个jlink右键&…