每日一博 | Promise 规范与原理解析
摘要
Promise对象用于清晰的处理异步任务的完成,返回最终的结果值,本次分享主要介绍Promise的基本属性以及Promise内部的基础实现,能够帮我们更明确使用场景、更快速定位问题。
Promise出现的原因
首先我们先来看一段代码:异步请求的层层嵌套
function fn1(params) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn1Data = {name: 'fn1'}
console.log(fn1Data, 'fn1Data');
// 请求2
(function fn2() {
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn2Data = {name: `${fn1Data.name}-fn2`}
console.log(fn2Data, 'fn2Data');
// 请求3
(function fn2() {
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn3Data = {name: `${fn2Data.name}-fn3`}
console.log(fn3Data, 'fn3Data');
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
})()
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
})()
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
}
fn1()
或者我们可以将上面的代码优化为下面这样
function fn1(params) {
console.log(`我是fn1,我在函数${params}中执行!!!`);
}
function fn2(params) {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log(`我是fn2,我在函数${params}中执行!!!结果是:`,params.data);
fn1('fn2')
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
} catch (error) {
console.error(error);
}
}
function fn3() {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log('fn3请求已完成');
fn2('fn3')
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
console.log('我是f3函数呀');
} catch (error) {
console.error(error);
}
}
fn3()
由上面的两种写法的请求可见,在promise之前,为了进行多个异步请求并且依赖上一个异步请求的结果时,我们必须进行层层嵌套,大多数情况下,我们又对异步结果进行数据处理,这样使得我们的代码非常难看,并且难以维护,这就形成了回调地狱,由此Promise开始出现了。
回调地狱缺点
- 代码臃肿
- 可读性差
- 耦合性高
- 不好进行异常处理
Promise的基本概念
含义
- ES6将其写进了语言标准里统一了用法,是一个构造函数,用来生成Promise实例
- 参数为一个执行器函数(执行器函数是立即执行的),该函数有两个函数作为参数,第一个参数是成功时的回调,第二个参数是失败时的回调
- 函数的方法有resolve(可以处理成功和失败)、reject(只处理失败)、all等方法
- then、catch、finally方法为Promise实例上的方法
状态
- pending --- 等待状态
- Fulfilled --- 执行状态 (resolve回调函数,then)
- Rejected --- 拒绝状态 (reject回调函数,catch)
- 状态一旦改变就不会再变,状态只可能是两种改变,从pending->Fulfilled,pending->Rejected
- 有两个关键的属性:PromiseState --- 状态改变,PromiseResult --- 结果数据改变
const p1 = Promise.resolve(64)
const p2 = Promise.reject('我错了')
const p3 = Promise.then()
const p4 = Promise.catch()
// 状态改变PromiseState 结果改变PromiseResult
console.log(new Promise(()=>{}), 'Promise'); // PromiseState='pending' PromiseResult=undefined
console.log(p1,'p1'); // PromiseState='Fulfilled' PromiseResult=64
console.log(p2,'p2'); // PromiseState="Rejected" PromiseResult='我错了'
console.log(p3, 'p3'); // then为实例上的方法,报错
console.log(p4, 'p4'); // catch为实例上的方法,报错
特点
结果由什么决定
resolve
- 如果传递的参数是非Promise类型的对象,则返回的结果是成功状态的Promise对象,进入下一个then里面
- 如果传递的参数是Promise类型的对象,则返回的结果由返回的Promise决定,如果返回的是resolve则是成功的状态,进入下一个then里,如果返回的是reject则是失败的状态,进入下一个catch里
reject
- 如果传递的参数是非Promise类型的对象,则返回的结果是拒绝状态的Promise对象,进入下一个catch里面或者是下一个then的第二个参数reject回调里面
- 如果传递的参数是Promise类型的对象,则返回的结果由返回的Promise决定,如果返回的是resolve则是成功的状态,进入下一个then里,如果返回的是reject则是拒绝的状态,进入下一个catch里面或者是下一个then的第二个参数reject回调里面
这在我们自己封装的API里面也有体现:为什么code为1时都是then接收,其他都是catch接收,就是因为在then里面也就是resolve函数中对code码进行了判断,如果是1则返回Promise.resolve(),进入then里处理,如果是非1则返回Promise.reject(),进入catch里处理。
流程图
简单使用
// 模拟一个promise的get请求
let count = 0
function customGet(url){
count += 1
return new Promise((resolve, reject)=>{
const xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET",url, true);
xmlHttp.onload = ()=>{
console.log(xmlHttp, 'xmlHttp---onload');
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log('customGet请求成功了');
// 返回非Promise,结果为成功状态
resolve({data:`第${count}次请求获取数据成功`})
// 返回Promise,结果由Promise决定
// resolve(Promise.reject('resolve中返回reject'))
} else {
reject('customGet请求错误了')
}
}
// Promise状态改变就不会再变
// onreadystatechange方法会被执行四次
// 当地次进来的时候,readyState不等于4,执行else逻辑,执行reject,状态变为Rejected,所以即使再执行if,状态之后不会再改变
// xmlHttp.onreadystatechange = function(){
// console.log(xmlHttp,'xmlHttp---onreadystatechange')
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// console.log('customGet请求成功了');
// resolve({data:`第${count}次请求获取数据成功`})
// } else {
// reject('customGet请求错误了')
// }
// }
xmlHttp.send();
})
}
// 使用Promise,并且进行链式调用
customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{
console.log(res.data);
return '第一次请求处理后的数据'
}).then((data)=>{
console.log(data)
// console.log(data.toFixed());
return customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=')
}).then((res)=>{
console.log(res.data);
}).catch((err)=>{
// 以类似'冒泡'的性质再外层捕获所有的错误
console.error(err, '这是catch里的错误信息');
})
手写实现简单的Promise
通过上面的回顾,我们已经了解了Promise的关键属性和特点,下面我们一起来实现一个简单的Promise吧
// 1、封装一个Promise构造函数,有一个函数参数
function Promise(executor){
// 7、添加对象属性PromiseState PromiseResult
this.PromiseState = 'pending'
this.PromiseResult = null
// 14、创建一个保存成功失败回调函数的属性
this.callback = null
// 8、this指向问题
const that = this
// 4、executor有两个函数参数(resolve,reject)
function resolve(data){
// 10、Promise状态只能修改一次(同时记得处理reject中的状态)
if(that.PromiseState !== 'pending') return
// console.log(this, 'this');
// 5、修改对象的状态PromiseState
that.PromiseState = 'Fulfilled'
// 6、修改对象的结果PromiseResult
that.PromiseResult = data
// 15、异步执行then里的回调函数
if(that.callback?.onResolve){
that.callback.onResolve(that.PromiseResult)
}
}
function reject(data){
console.log(that.PromiseState, 'that.PromiseState');
if(that.PromiseState !== 'pending') return
// 9、处理失败函数状态
that.PromiseState = 'Rejected'
that.PromiseResult = data
console.log(that.PromiseResult, 'that.PromiseResult');
console.log(that.PromiseState, 'that.PromiseState');
// 16、异步执行then里的回调函数
if(that.callback?.onReject){
that.callback.onReject(that.PromiseResult)
}
}
// 3、执行器函数是同步调用的,并且有两个函数参数
executor(resolve,reject)
}
// 2、函数的实例上有方法then
Promise.prototype.then = function(onResolve,onReject){
// 20、处理onReject没有的情况
if(typeof onReject !== 'function'){
onReject = reason => {
throw reason
}
}
// 21、处理onResolve没有的情况
if(typeof onResolve !== 'function'){
onResolve = value => value
}
// 17、每一个then方法都返回一个新的Promise,并且把上一个then返回的结果传递出去
return new Promise((nextResolve,nextReject)=>{
// 11、处理成功或失败
if(this.PromiseState === 'Fulfilled'){
// 12、将结果传递给函数
// onResolve(this.PromiseResult)
// 18、拿到上一次执行完后返回的结果,判断是不是Promise
const result = onResolve(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
},(r)=>{
nextReject(r)
})
} else {
nextResolve(result)
}
}
// 当你一步步写下来的时候有没有怀疑过为什么不用else
if(this.PromiseState === 'Rejected'){
// 第12步同时处理此逻辑
// onReject(this.PromiseResult)
// 22、处理catch异常穿透捕获错误
try {
const result = onReject(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
}).catch((r)=>{
nextReject(r)
})
} else {
nextReject(result)
}
} catch (error) {
nextReject(this.PromiseResult)
}
}
// 13、异步任务时处理成功或失败,想办法等异步任务执行完成后才去执行这两个函数
if(this.PromiseState === 'pending'){
this.callback = {
onResolve,
onReject
}
console.log(this.callback, 'this.callback');
}
})
}
// 19、函数实例上有方法catch
Promise.prototype.catch = function(onReject) {
return this.then(null,onReject)
}
// 使用自定义封装的Promise
const customP = new Promise((resolve,reject)=>{
// 模拟异步执行请求
// const xmlHttp = new XMLHttpRequest();
// xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true);
// xmlHttp.onload = ()=>{
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// resolve('success')
// } else {
// reject('error')
// }
// }
// xmlHttp.send();
// 同步执行
resolve('success')
// reject('error')
})
console.log(customP, 'customP');
customP.then((res)=>{
console.log(res, 'resolve回调');
return '第一次回调'
// return new Promise((resolve,reject)=>{
// reject('错错错')
// })
},(err)=>{
console.error(err, 'reject回调');
return '2121'
}).then(()=>{
console.log('then里面输出');
}).then().catch((err)=>{
console.error(err, 'catch里的错误');
})
针对resolve中返回Promise对象时的内部执行顺序
总结
以上就是我们常用的Promise基础实现,在实现过程中对比了Promise和函数嵌套处理异步请求的优缺点,Promise仍存在缺点,但是的确方便很多,同时更清晰的理解到错误处理如何进行异常穿透的,也能帮助我们更规范的使用Promise以及快速定位问题所在。
作者:京东物流 孙琦
来源:京东云开发者社区 自猿其说Tech 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
新一代基于 mybatis 的 orm:mybatis-mp 1.1.7 发布
全新一代基于 mybatis 扩展的 orm 框架,拥有良好的性能和扩展性。 1:基于注解,映射数据库; 2:支持多表 join 和返回; 3:api 采用 lambda 和 stream 流式设计; 4:内置分页以及超强的 sql 优化功能; 5:稳定且性能极优; 6:轻量级封装 mybatis,几乎没有侵入; 7:api 丰富,支持数据库函数、多表、乐观锁、多租户 等众多功能;零学习成本,和写 sql 一样方便。 8:内置代码生成器,通过配置,可定制自身规范; 9:全新的框架,全新的设计理念,新的就是好! 本次 1.1.7 更新 内容: 1. 升级mybatis,spring,spring-boot版本 2. 采用junit5 3. 规范底层接口命名 4. 规范,整理maven依赖
-
下一篇
关于 deepin V23 的发热、续航问题,我们做了电源配置优化
之前一些用户反馈,在使用 deepin (深度)操作系统过程中遇到了 CPU 功耗过高导致的设备发热、续航较差情况,而在这些负载场景下,CPU 的占用往往不高。为了解决这个痛点,deepin 团队特别成立专项计划,对于 deepin 的电源进行专项优化。 在对电源进行专项优化之前,首先对 deepin (深度)操作系统进行了深入调查和分析,以了解其在负载场景下的实际运行情况。 经过对 CPU 使用率和功耗的监测,我们发现了一个令人惊讶的事实:尽管在高负载场景下 CPU 的占用率不高,但其功耗却持续升高,最终导致设备发热并影响续航。也就是说,我们前期做的省电优化工作,不仅无效,还起了反作用。那么到底该如何解决这一问题呢? 在刚刚结束的DDUC 2023上,deepin(深度)社区用户 fslong 从社区参与共创的角度,分享了帮助 deepin 团队一起完成优化工作的故事。那么,本文就让我们从 deepin 团队研发工程师的角度来回顾解决这一问题的历程吧! 找到问题的根源 1.内核 最开始发现问题的地方在内核。有用户将 deepin 的内核和 Ubuntu 的内核进行对比后发现,虽然二者...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- MySQL数据库在高并发下的优化方案
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2配置默认Tomcat设置,开启更多高级功能