深入理解Promise

news2024/9/17 9:02:49

Promise的前提概念

Promise是一个构造函数,用来生成Promise实例
Promise构造函数接受一个函数作为参数,该函数有两个参数,分别是resolvereject
resolve:成功时的回调
reject:失败时的回调

Promise分别有三个状态
1、pending :进行中
2、fulfilled:已成功
3、rejected:已失败

Promise的执行
Promise一旦新建后就会立马执行,无法中途取消,Promise返回的只有两种状态,并且该状态产生就无法更改。也就是说Promise的状态只有一下两种情况:
1、从pendingfulfilled
2、从pendingrejected
pending的状态一旦改变,就不会再发生变化。

Promise的结果返回
Promise内部结果一旦返回,resolvereject其中的任何一种,即可产生回调。
1、resolve的回调在then()中获取(成功的回调)
2、reject的回调在cathc()中获取(失败,捕获错误)

let promise = new Promise(function(resolve,reject){
     // sth...
     let i = 5;
     if( i > 1){
         resolve(i) // 成功
     } else { 
         reject(error) // 失败
     }
})
    
promise.then(el=>{
     // el即为resolve的参数
     console.log(el);  // 5
    })
    .catch(err=>{
        console.log(err);
    })

Promise的执行顺序
Promise创建后会立马执行,then()是一个异步回调,将在所有的同步任务执行完后执行then(),也就是说:Promise在新建的时候会立即还行,then()catch()为异步任务,当所有同步任务执行完后才会执行then()

let pro = new Promise(function(resolve,reject){
    console.log('Promise新建');
    resolve()
})

pro.then(el=>{
   console.log('.then回调');
})
console.log('同步任务');

在这里插入图片描述
上述代码的执行结果:
1、Promise新建
2、同步任务
3、then()回调


Promise参数和执行顺序

resolve函数的参数出了可以携带正常值以外,还可以携带Promise实例

let pro = new Promise(function(resolve,reject){
let i = 10
    if(i < 50){
        resolve(i)
    } else {
        reject(error)
   }
})

let pro2 = new Promise(function(resolve,reject){
    resolve(pro)  // 参数为上一个Promise实例
})
      
pro2.then(el=>{
     console.log(el);  // 10
    })
    .catch(err=>{
     console.log(err);
    })

执行顺序:
先执行pro,如果pro的状态是pending时,pro2则会等待执行,当pro的状态变成resolverejected时,pro2则会立马执行,由于pro决定pro2的状态,所以后面的then变成了针对pro


Promise的嵌套执行

let p = new Promise((resolve,reject)=>{
     let i = 10
     if(i > 5){
         resolve(i)
     } else {
         reject('pro错误')
     }
})

let p2 = new Promise((resolve,reject)=>{
    p.then(el=>{
        console.log(el * 2);  // 20
        if(el > 6){
        // 此处需要写成return resolve(el)看起来会更正常
           resolve(el)
           console.log('我是p1的.then回调');  // 我是p1的.then回调
        } else {
            reject('pro2错误')
        }
    })
   })
   
let p3 = new Promise((resolve,reject)=>{
    p2.then(el=>{
        if(el > 50){
            resolve(el)
        } else {
            reject('pro3错误')
        }
    })
})

多个Promise的执行顺序:
新建Promise后会立马执行,先执行Promise内的任务,执行完成后再执行then()
如果Promise内有其它Promisethen()任务执行(这里简称p2),会先将p2then()任务执行完毕,再执行自身的then()任务
多个Promise并行,会先按顺序执行Promise的任务,再分别按照顺序分别执行他们的then()任务

需要注意的是:上面p2Promse中,正常来说,resolvereject执行完后,Promise的使命应该完成,但此处因为执行顺序的问题,并没有先执行resolve,而是执行了console的任务,因该写成return resolve(value)的形式,让Promise更正常一些,否则会出现不必要的意外。


Promise.prototype.then()

then方法是定义在原型对象Promise.prototype上的,它可以为Promise实例添加状态改变时的回调函数。
then有两个参数(可选):
resolved:成功状态的回调函数
rejected:失败状态的回调函数
then返回的是一个新的Promise实例,因此可以采用链式写法,then()后面还可以接then()

let p = new Promise((resolve,reject)=>{
    let i = 5;
    if(i > 4){
        resolve(i)
    } 
})

p.then(el=>{
    console.log('then1:'+ el);
    if(el > 3){
       return el
    }
}).then(el=>{
    console.log('then2:'+ el * 2);
}).catch(err=>{
    console.log(err);
}
 /*
   输出结果:
   then1:5
   then2:10
*/

上面代码使用then方法,第一个then()回调函数执行完后,会将返回结果传入第二个回调函数,也就是说,第二个then()的参数是第一个then()返回的结果。
catch的捕获具有冒泡性质,也就是说,前面不论.then了多少次,里面发生的错误,都会被最后一个catch捕获到。


Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null,rejection)的别名,也就是说,catch其实是用来指定发生错误时的回调函数。

let p = new Promise((resolve,reject)=>{
        reject('错误信息')
   })
   
p.then(el=>{
    // sth...
  }).catch(err=>{
    console.log(err);  // 错误信息
  })

上述代码中,p方法返回Promise对象,该对象返回的是rejectthen()方法调用回调函数,then()有两个参数,一个resolvedrejected,如果Promise返回的是reject,那么.then的状态就会变成rejectedrejected会调用catch()方法指定的回调函数,处理该错误,catch的参数就是reject抛出的错误信息。

如果then运行中抛出错误,也会被catch()捕获

let p2 = new Promise((resolve,reject)=>{
    resolve(100)
   })
p2.then(el=>{
    throw new Error('then抛错')
}).catch(err=>{
    console.log(err);  // Error: then抛错
})

catch捕捉错误的方式

   // 方式一
let promise = new Promise(function(resolve,reject){
    try {
       throw new Error('方式一错误')
    } catch(e){
         reject(e)
    }
})

promise.catch(err=>{
   console.log(err);  //  方式一错误
   })
// 方式二
let promise2 = new Promise(function(resolve,reject){
    reject(new Error('方式二错误'))
})

promise2.catch(err=>{
      console.log(err);  //  方式二错误
   })
// 方式三
let promise3 = new Promise((resolve,reject)=>{
      reject('方式三错误')
   })
   
promise3.catch(err=>{
      console.log(err);  // 方式三错误
   })

以上三种效果是一样的,前两种通过Error抛出异常,后一种直接抛出异常
catch返回的也是一个Promise对象,后面还可以接着调用then()方法
如果Promise的状态变成resolved,那么再抛出错误是无效的,因为Promise的状态一旦发生改变,就会永久保持该状态。

执行阻塞问题
Promise内部错误不会影响到到异步队列中的Promise外部代码,同步代码还是会造成阻塞

let p3 = new Promise((resolve,reject)=>{
      resolve(str)  // ReferenceError: str is not defined
})

p3.then(el=>{console.log(el)})
setTimeout(()=>{console.log('Promise外部代码')},1000)  // Promise外部代码
console.log('同步代码');  // 未打印

在这里插入图片描述
Promise在建立时会立即执行,此处报错了,所以后面的同步代码都无法执行,但是异步的setTimeout还是会执行,并不受影响。


finally()

finally有最后、终于的意思,也就是说finally()方法不管Promise最后的状态如何,都会执行

let p = new Promise((resolve,reject)=>{
            resolve('东方不败')
})
p.then(el=>{
    console.log(el);  // 东方不败
})
.catch(err=>{
    console.log(err);
})
.finally(()=>
    console.log('finally执行')  // finally执行
)

在这里插入图片描述
上述代码中,不管Promise最后的状态怎么样,finally都会执行,即使没有then()catch()finally也会执行

需要注意的是,finally不接受任何参数,但是finally的执行顺序跟内部是否是一个函数有关,如果finally内部是一个函数,那么finally会在Promise最后执行,如果finally内部没有任何函数,直接输出,那么finally会在Promise最前面执行。

finally的执行跟Promise的状态无关。


Promise.all()

Promise.all()方法用于将多个Promise实例包装成一个新的Promise实例
语法:let p = Promise.all([p1,p2,p3])

let p = new Promise((resolve,reject)=>{
    resolve(10)
})
p.then(el=>{
    console.log(el);
})
        
let p2 = new Promise((resolve,reject)=>{
    // resolve(20)
    reject(20)
})
p2.then(el=>{
    console.log(el);
})
.catch(err=>{console.log('p2:',err);})

// p、p2都返回resolve则触发该函数
Promise.all([p,p2])
       .then(el=>{
           console.log(el);
        })
        .catch(err=>{console.log('all:',err);})  // all:20

Promise.all的两个promise参数状态都变成fulfilled时,才会触发Promise.all()的方法,如果其中以一个参数被rejected,那么Promise.all()也会变成rejected的状态,会被catch捕捉。
上面代码中,如果p2没有chach方法,则会调用Promise.all()catch


race()

Promise.race()方法可以将多个Promise实例包装成一个新的Promise实例。
语法: let p = Promise.race([p1,p2,p3])

let p = new Promise((resolve,reject)=>{
      setTimeout(()=>{
            resolve('p1状态')
      },2000)
    })
    
let p2 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
            resolve('p2状态')
       },1000)
     })
     
let p3 = new Promise((resolve,reject)=>{
       setTimeout(()=>{
            resolve('p3状态')
       },3000)
     })
     
let p4 = new Promise((resolve,reject)=>{
       setTimeout(()=>{
            reject('reject:p4状态')
        },4000)
      })

let promise = Promise.race([p,p2,p3,p4])
promise
.then(el=>{
    console.log(el);  // p2状态
})
.catch(err=>{
    console.log(err);
})

在这里插入图片描述

race有竞争,角逐的意思,该方法其实就是参数中,哪一个Promise的状态先发生改变,就调用先发生改变的promise,这个el就是最先发生改变的promise的返回值。


Promise.allSettled()

Promise.allSettled()方法可以在所有异步操作都结束的情况下,调用该方法。它与Promise.all()方法不同的是,all()方法需要所有异步都成功才会进入then(),而allSettled()不需要。

let p = new Promise((resolve,reject)=>{
      resolve('p1状态')
})
        
let p2 = new Promise((resolve,reject)=>{
       setTimeout(()=>{
         reject('p2状态')
       },3000)
})

Promise.allSettled([p,p2])
.then(el=> console.log(el) )

在这里插入图片描述
Promise.allSettled()的参数不管是reject还是resolve都会触发,只要参数的状态全都发生改变即可,上面代码中,p2延迟了3s状态才发生改变,那么Promise.allSettled()则会等待参数的状态全都发生改变时触发。


Promise.any()

Promise.any()方法接受一组Promise实例作为参数,包装成一个新的Promise实例返回。

let p = new Promise((resolve,reject)=>{
      resolve('p1状态')
})
        
let p2 = new Promise((resolve,reject)=>{
      reject('p2状态')
})

Promise.any([p,p2])
       .then(el=>{
          console.log('then:',el);  // then: p1状态
       })
       .catch(err=>{
          console.log('catch:',err);
       })

只要有一个参数的状态变成fulfilledPromise.any就会变成fulfilled状态
如果所有的参数实例都变成rejected状态,包装实例才会变成rejected状态
也就是说,any是只要有一个参数成功,any就可以then,所有参数都rejectany才会catch


Promise.resolve()

Promise.resolve()可以将现有对象转为Promise对象,Promise.resolve()方法就起到这个作用。
语法:let p = Promise.resolve(obj)

let p = Promise.resolve('obj')
p.then(el=> console.log(el))  // obj

// 其实相当于
let p2 = new Promise(resolve=>resolve('obj'))

如果Promise.resolve()没有参数,那么将直接返回resolved状态的Promise对象,并且是一个没有参数的then()方法。

let p3 = Promise.resolve()
p3.then(el=>{
     console.log(el);  // undefined
})


setTimeout(()=>{
     console.log('定时器');
},0)

Promise.resolve()
       .then(()=>{
         setTimeout(()=>{console.log('resolve内定时器')},0)
         console.log('resolve内部');
        })
console.log('外部');

在这里插入图片描述
Promise的执行顺序,Promise是一个异步的微任务,同步任务执行完后才会执行异步任务,异步的微任务会先执行,微任务执行完后才会执行异步的宏任务,此处的setTimeout是异步的宏任务,所以会最后执行,外部的定时器是异步的宏任务,Promise内部的定时器是异步微任务内的宏任务,所以微任务内的宏任务会更慢执行,此处的执行顺序为:外部、resolve内部、定时器、resolve内定时器。


Promise.reject()

Promise.reject()返回一个新的Promise实例,该实例的状态为rejected

let p = Promise.reject('错误信息')
p.catch(err=>console.log(err))

// 等同于
let p2 = new Promise((resolve,reject)=>{
    reject('错误信息2')
})
p2.catch(err=>console.log(err))

在这里插入图片描述


Promise.try()

实际开发中,会遇到这么一种情况,不管函数是同步还是异步的,都想用Promise来处理它,因为这样就可以用then()指定下一步流程,catch处理抛出的错误,更便于管理流程。
基于Promise我们可以用async做到同步的代码风格执行异步操作,现在有一个最新的提案,使用Promise.try()来捕获所有的同步和异步错误。
为什么这么做?
Promise抛出的错误,catch可以捕获到,但此处仅限于当前Promise的异常错误,也就是异步的。如果同步错误抛出,那么catch()是捕捉不到的,需要在外层再嵌套一层try...catch,这样写起来非常麻烦,代码如下:

try {
   let p = new Promise((resolve,reject)=>{
           reject('异步错误')
       })
   p.catch(err=> void console.log(err) )  // 异步错误
   throw Error('同步错误')
      
} catch (e) {
    console.log(e);  // 同步错误
}
// 这样catch两次,可以处理同步和异步的抛错问题

如果使用Promise.try()就可以捕获所有的同步和异步错误

Promise.try(()=>{
    let p2 = new Promise((resolve,reject)=>{
        reject('异步错误2')
    })    
         throw Error('同步错误2')
    })
    .then(el=>{
        console.log('then:',el);
     })
    .catch(err=>{
        console.log('catch:',err);
    })

此处的Promise.try就相当于try代码块,Promise.catch就相当于catch代码块。


案例源码:https://gitee.com/wang_fan_w/es6-science-institute

如果觉得这篇文章对你有帮助,欢迎点亮一下star哟

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

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

相关文章

行人属性识别研究综述(一)

文章目录摘要1、简介2 问题的表述和挑战3 标准3.1 数据集3.2 评价标准4 行人属性识别的常规流程4.1 多任务学习4.2 多标签学习5 深度神经网络&#x1f407;&#x1f407;&#x1f407;&#x1f407;&#x1f407;&#x1f407;&#x1f407; 欢迎阅读 【AI浩】 的博客&#x1f…

C#上位机基础学习_基于S7.Net实现读取S7-1500PLC中的字符串变量

C#上位机基础学习_基于S7.Net实现读取S7-1500PLC中的字符串变量 如下图所示,首先在TIA博途中创建一个项目,添加一个1500PLC,添加一个DB块,在DB块中添加几个字符串变量, 如下图所示,打开Visual Studio 2019,新建一个项目,在Form1中添加一个按钮和一个文本框, 如下图…

linux——高级信号

高级信号的收发发&#xff1a;siquequ收&#xff1a;sigaction() 包含三个元素num,sigaction()函数&#xff0c;备份num ->signumsigaction是一个结构体&#xff0c;需额外配置再传进来备份直接忽略&#xff0c;代表不需要备份sigaction结构体又包含四个元素sa_handler&…

Git进阶:修改上次提交 git commit --amend

一、问题说明 git commit 后&#xff0c;发现刚才的备注写错了&#xff0c;或者代码漏掉了&#xff0c;这时我们肯定是想取消刚才的提交。此刻有两种方法 &#xff08;1&#xff09;使用git reset命令将刚才的提交会退掉。需要注意的是git reset --soft 和git reset --hard的区…

【附源码】国内首届Discord场景创意编程开源项目

以下开源项目是由环信联合华为举办的《国内首届Discord场景创意编程赛》作品&#xff0c;附源码&#xff0c;一键即用。 一、 模拟器游戏直播-新新人类 新新人类模拟器游戏直播基于环信超级社区Demo构建&#xff0c;增加以“video-x”命名的新型Channel&#xff0c;用户可在本…

Java三目运算符导致 NPE

在三目运算符中&#xff0c;表达式 1 和 2 在涉及算术计算或数据类型转换时&#xff0c;会触发自动拆箱。当其中的操作数为 null 值时&#xff0c;会导致 NPE 。 一、基础知识 三目运算符 三目运算符是 Java 语言中的重要组成部分&#xff0c;它也是唯一有 3 个操作数的运算…

Linux常用命令——tempfile命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tempfile shell中给临时文件命名 补充说明 有时候在写Shell脚本的时候需要一些临时存储数据的才做&#xff0c;最适合存储临时文件数据的位置就是/tmp&#xff0c;因为该目录中所有的内容在系统重启后就会被清…

LInux(三)程序地址空间、内存管理

目录 一、程序地址空间 二、内存管理方式 1、分段式内存管理 2、分页式内存管理 3、段页式内存管理 三、关于内存管理内容补充&#xff08;分页式&#xff09; 1、页表简单呈现 2、访问权限位 3、缺页中断 4.内存置换算法 一、程序地址空间 创建父子进程同时访问同一变量…

正式发布丨Azure OpenAI Service

Azure OpenAI 服务现已在微软全球 Azure 平台正式发布&#xff0c;这是微软人工智能大众化以及与 OpenAI 持续合作的又一里程碑。 大型语言模型正迅速成为用户无限创新、应用 AI 解决重要问题的平台。随着 Azure OpenAI 服务的正式发布&#xff0c;更多企业用户可以访问世界先…

在线支付系列【5】支付安全之数字证书

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录公钥信任问题数字证书颁发流程使用流程SSL数字证书HTTPSSSL/TLS 证书如何申请SSL 证书使用keytool 生成ssl证书通信过程公钥信任问题 之前我们使用对称、非对称加密、信息摘要、数字签名&#…

DPDK系列之一基础环境搭建

一、DPDK是什么 DPDK,Data Plane Development Kit&#xff0c;数据平面开发套装&#xff0c;它还有一个兄弟SPDK&#xff0c;专门用来做存储优化的。它主要运行于Linux&#xff0c;是由Intel几家公司共同开发&#xff0c;用于快速处理数据的一个网络开发工具套装。更详细的说明…

python数据清洗1

数据获取——》数据清洗——》数据转换——》数据分析 通过设置步长&#xff0c;有间隔的取元素通过设置步长为-1&#xff0c;将元素颠倒 数据清洗工具 目前在Python中, numpy和pandas是最主流的工具。 Numpy中的向量化运算使得数据处理变得高效&#xff1b;Pandas提供了大量…

【Java入门】Java数据类型

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;Java入门 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又一…

(十四)线程池

线程池线程池是一组线程的集合。线程池维护一个队列&#xff0c;调用者向这个队列中添加任务&#xff0c;而线程池中的线程则不停地从队列中取出任务执行。线程池的继承关系如下图&#xff0c;其中ThreadPoolExecutor和ScheduledThreadPoolExecutor是具体的实现。ThreadPoolExe…

LeetCode刷题模版:141 - 150

目录 简介141. 环形链表142. 环形链表 II143. 重排链表144. 二叉树的前序遍历145. 二叉树的后序遍历146. LRU 缓存【未实现】147. 对链表进行插入排序148. 排序链表149. 直线上最多的点数150. 逆波兰表达式求值结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方…

多轮对话(三):Spoken Language Understanding 进展和前沿

本篇博客基于哈工大发表在IJCAI上的论文&#xff1a;A Survey on Spoken Language Understanding - Recent Advances and New Frontiers。 论文链接 github链接 口语理解&#xff08;SLU&#xff09;旨在提取用户查询的语义框架&#xff0c;是面向任务的对话系统的核心组件。本…

excel函数技巧:两个查询函数的用法比较 上篇

EXCEL函数江湖烽烟再起&#xff0c;函数大擂台迎来两位重量级选手。守擂者是号称全民偶像、人见人爱车见车载的巨星级函数VLOOKUP&#xff0c;挑战者则是名气不大实力强劲高手的LOOKUP函数&#xff01;这对与生俱来的对手&#xff0c;究竟会在函数擂台上擦出怎样的火花&#xf…

Nginx原理

一、master和worker二、worker当客户端发送请求&#xff0c;先到达master,master通知所有的worker,然后所有的worker开始竞争任务。三、一个master和多个worker有什么好处&#xff08;1&#xff09;可以使用nginx -s reload热部署&#xff0c;利用nginx进行热部署&#xff08;2…

8、MariaDB11数据库安装初始化密码Navicat连接

MariaDB11安装 安装前准备 下载安装包 点我去MariaDB官网下载安装包 查看相关文档 Mariadb Server官方文档 使用zip安装 解压缩zip 将下载到的zip解压缩到想安装的位置。 生成data目录 打开cmd并进入到刚才解压后的bin目录&#xff0c; 执行mysql_install_db.exe程序生…

Python异步编程Future对象详解

今天继续给大家介绍Python相关知识&#xff0c;本文主要内容是Python异步编程Future对象详解。 一、Python Future对象简介 在上文Python Task对象详解中&#xff0c;我们介绍到了Task对象&#xff0c;而Future对象是Task对象的基类&#xff0c;比Task更加底层。一个Future是…