Promise规范与原理解析 | 京东物流技术团队

摘要

Promise对象用于清晰的处理异步任务的完成,返回最终的结果值,本次分享主要介绍Promise的基本属性以及Promise内部的基础实现,能够帮我们更明确使用场景、更快速定位问题。

Promise出现的原因

首先我们先来看一段代码:异步请求的层层嵌套

1function fn1(params) { 2 const xmlHttp = new XMLHttpRequest(); 3 xmlHttp.onreadystatechange = function(){ 4 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 5 const fn1Data = {name: 'fn1'} 6 console.log(fn1Data, 'fn1Data'); 7 // 请求2 8 (function fn2() { 9 xmlHttp.onreadystatechange = function(){ 10 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 11 const fn2Data = {name: `${fn1Data.name}-fn2`} 12 console.log(fn2Data, 'fn2Data'); 13 // 请求3 14 (function fn2() { 15 xmlHttp.onreadystatechange = function(){ 16 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 17 const fn3Data = {name: `${fn2Data.name}-fn3`} 18 console.log(fn3Data, 'fn3Data'); 19 } 20 } 21 xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true); 22 xmlHttp.send(); 23 })() 24 } 25 } 26 xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true); 27 xmlHttp.send(); 28 })() 29 } 30 } 31 xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true); 32 xmlHttp.send(); 33} 34 35fn1() 36 37 38

或者我们可以将上面的代码优化为下面这样

1function fn1(params) { 2 console.log(`我是fn1,我在函数${params}中执行!!!`); 3} 4 5function fn2(params) { 6 try { 7 const xmlHttp = new XMLHttpRequest(); 8 xmlHttp.onreadystatechange = function(){ 9 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 10 console.log(`我是fn2,我在函数${params}中执行!!!结果是:`,params.data); 11 fn1('fn2') 12 } 13 } 14 xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true); 15 xmlHttp.send(); 16 } catch (error) { 17 console.error(error); 18 } 19} 20 21function fn3() { 22 try { 23 const xmlHttp = new XMLHttpRequest(); 24 xmlHttp.onreadystatechange = function(){ 25 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 26 console.log('fn3请求已完成'); 27 fn2('fn3') 28 } 29 } 30 xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true); 31 xmlHttp.send(); 32 console.log('我是f3函数呀'); 33 } catch (error) { 34 console.error(error); 35 } 36} 37 38fn3() 39 40 41

由上面的两种写法的请求可见,在promise之前,为了进行多个异步请求并且依赖上一个异步请求的结果时,我们必须进行层层嵌套,大多数情况下,我们又对异步结果进行数据处理,这样使得我们的代码非常难看,并且难以维护,这就形成了回调地狱,由此Promise开始出现了。

回调地狱缺点

  • 代码臃肿
  • 可读性差
  • 耦合性高
  • 不好进行异常处理

Promise的基本概念

含义

  1. ES6将其写进了语言标准里统一了用法,是一个构造函数,用来生成Promise实例
  2. 参数为一个执行器函数(执行器函数是立即执行的),该函数有两个函数作为参数,第一个参数是成功时的回调,第二个参数是失败时的回调
  3. 函数的方法有resolve(可以处理成功和失败)、reject(只处理失败)、all等方法
  4. then、catch、finally方法为Promise实例上的方法

状态

  1. pending --- 等待状态
  2. Fulfilled --- 执行状态 (resolve回调函数,then)
  3. Rejected --- 拒绝状态 (reject回调函数,catch)
  4. 状态一旦改变就不会再变,状态只可能是两种改变,从pending->Fulfilled,pending->Rejected
  5. 有两个关键的属性:PromiseState --- 状态改变,PromiseResult --- 结果数据改变
1const p1 = Promise.resolve(64) 2const p2 = Promise.reject('我错了') 3const p3 = Promise.then() 4const p4 = Promise.catch() 5 6// 状态改变PromiseState 结果改变PromiseResult 7console.log(new Promise(()=>{}), 'Promise'); // PromiseState='pending' PromiseResult=undefined 8console.log(p1,'p1'); // PromiseState='Fulfilled' PromiseResult=64 9console.log(p2,'p2'); // PromiseState="Rejected" PromiseResult='我错了' 10console.log(p3, 'p3'); // then为实例上的方法,报错 11console.log(p4, 'p4'); // catch为实例上的方法,报错 12 13

特点

  1. 错误信息清晰定位:可以在外层捕获异常信息(网络错误、语法错误都可以捕获),有“冒泡”性质,会一直向后传递,直到被捕获,所以在最后写一个catch就可以了

  2. 链式调用:每一个then和catch都会返回一个新的Promise,把结果传递到下一个then/catch中,因此可以进行链式调用 --- 代码简洁清晰

结果由什么决定

resolve

  1. 如果传递的参数是非Promise类型的对象,则返回的结果是成功状态的Promise对象,进入下一个then里面
  2. 如果传递的参数是Promise类型的对象,则返回的结果由返回的Promise决定,如果返回的是resolve则是成功的状态,进入下一个then里,如果返回的是reject则是失败的状态,进入下一个catch里

reject

  1. 如果传递的参数是非Promise类型的对象,则返回的结果是拒绝状态的Promise对象,进入下一个catch里面或者是下一个then的第二个参数reject回调里面
  2. 如果传递的参数是Promise类型的对象,则返回的结果由返回的Promise决定,如果返回的是resolve则是成功的状态,进入下一个then里,如果返回的是reject则是拒绝的状态,进入下一个catch里面或者是下一个then的第二个参数reject回调里面

这在我们自己封装的API里面也有体现:为什么code为1时都是then接收,其他都是catch接收,就是因为在then里面也就是resolve函数中对code码进行了判断,如果是1则返回Promise.resolve(),进入then里处理,如果是非1则返回Promise.reject(),进入catch里处理。

流程图

简单使用

1// 模拟一个promise的get请求 2let count = 0 3function customGet(url){ 4 count += 1 5 return new Promise((resolve, reject)=>{ 6 const xmlHttp = new XMLHttpRequest(); 7 xmlHttp.open("GET",url, true); 8 xmlHttp.onload = ()=>{ 9 console.log(xmlHttp, 'xmlHttp---onload'); 10 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 11 console.log('customGet请求成功了'); 12 // 返回非Promise,结果为成功状态 13 resolve({data:`${count}次请求获取数据成功`}) 14 15 // 返回Promise,结果由Promise决定 16 // resolve(Promise.reject('resolve中返回reject')) 17 } else { 18 reject('customGet请求错误了') 19 } 20 } 21 22 // Promise状态改变就不会再变 23 // onreadystatechange方法会被执行四次 24 // 当地次进来的时候,readyState不等于4,执行else逻辑,执行reject,状态变为Rejected,所以即使再执行if,状态之后不会再改变 25 // xmlHttp.onreadystatechange = function(){ 26 // console.log(xmlHttp,'xmlHttp---onreadystatechange') 27 // if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 28 // console.log('customGet请求成功了'); 29 // resolve({data:`第${count}次请求获取数据成功`}) 30 // } else { 31 // reject('customGet请求错误了') 32 // } 33 // } 34 xmlHttp.send(); 35 }) 36 } 37 38// 使用Promise,并且进行链式调用 39customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{ 40 console.log(res.data); 41 return '第一次请求处理后的数据' 42}).then((data)=>{ 43 console.log(data) 44 // console.log(data.toFixed()); 45 return customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=') 46}).then((res)=>{ 47 console.log(res.data); 48}).catch((err)=>{ 49 // 以类似'冒泡'的性质再外层捕获所有的错误 50 console.error(err, '这是catch里的错误信息'); 51}) 52 53 54

手写实现简单的Promise

通过上面的回顾,我们已经了解了Promise的关键属性和特点,下面我们一起来实现一个简单的Promise吧

1 // 1、封装一个Promise构造函数,有一个函数参数 2 function Promise(executor){ 3 // 7、添加对象属性PromiseState PromiseResult 4 this.PromiseState = 'pending' 5 this.PromiseResult = null 6 7 // 14、创建一个保存成功失败回调函数的属性 8 this.callback = null 9 10 // 8、this指向问题 11 const that = this 12 13 // 4、executor有两个函数参数(resolve,reject) 14 function resolve(data){ 15 // 10、Promise状态只能修改一次(同时记得处理reject中的状态) 16 if(that.PromiseState !== 'pending') return 17 18 // console.log(this, 'this'); 19 // 5、修改对象的状态PromiseState 20 that.PromiseState = 'Fulfilled' 21 22 // 6、修改对象的结果PromiseResult 23 that.PromiseResult = data 24 25 // 15、异步执行then里的回调函数 26 if(that.callback?.onResolve){ 27 that.callback.onResolve(that.PromiseResult) 28 } 29 } 30 function reject(data){ 31 console.log(that.PromiseState, 'that.PromiseState'); 32 if(that.PromiseState !== 'pending') return 33 34 // 9、处理失败函数状态 35 that.PromiseState = 'Rejected' 36 that.PromiseResult = data 37 console.log(that.PromiseResult, 'that.PromiseResult'); 38 console.log(that.PromiseState, 'that.PromiseState'); 39 40 // 16、异步执行then里的回调函数 41 if(that.callback?.onReject){ 42 that.callback.onReject(that.PromiseResult) 43 } 44 } 45 // 3、执行器函数是同步调用的,并且有两个函数参数 46 executor(resolve,reject) 47 } 48 // 2、函数的实例上有方法then 49 Promise.prototype.then = function(onResolve,onReject){ 50 // 20、处理onReject没有的情况 51 if(typeof onReject !== 'function'){ 52 onReject = reason => { 53 throw reason 54 } 55 } 56 // 21、处理onResolve没有的情况 57 if(typeof onResolve !== 'function'){ 58 onResolve = value => value 59 } 60 // 17、每一个then方法都返回一个新的Promise,并且把上一个then返回的结果传递出去 61 return new Promise((nextResolve,nextReject)=>{ 62 // 11、处理成功或失败 63 if(this.PromiseState === 'Fulfilled'){ 64 // 12、将结果传递给函数 65 // onResolve(this.PromiseResult) 66 67 // 18、拿到上一次执行完后返回的结果,判断是不是Promise 68 const result = onResolve(this.PromiseResult) 69 if(result instanceof Promise){ 70 result.then((v)=>{ 71 nextResolve(v) 72 },(r)=>{ 73 nextReject(r) 74 }) 75 } else { 76 nextResolve(result) 77 } 78 } 79 // 当你一步步写下来的时候有没有怀疑过为什么不用else 80 if(this.PromiseState === 'Rejected'){ 81 // 第12步同时处理此逻辑 82 // onReject(this.PromiseResult) 83 84 // 22、处理catch异常穿透捕获错误 85 try { 86 const result = onReject(this.PromiseResult) 87 if(result instanceof Promise){ 88 result.then((v)=>{ 89 nextResolve(v) 90 }).catch((r)=>{ 91 nextReject(r) 92 }) 93 } else { 94 nextReject(result) 95 } 96 } catch (error) { 97 nextReject(this.PromiseResult) 98 } 99 } 100 101 // 13、异步任务时处理成功或失败,想办法等异步任务执行完成后才去执行这两个函数 102 if(this.PromiseState === 'pending'){ 103 this.callback = { 104 onResolve, 105 onReject 106 } 107 console.log(this.callback, 'this.callback'); 108 } 109 }) 110 } 111 // 19、函数实例上有方法catch 112 Promise.prototype.catch = function(onReject) { 113 return this.then(null,onReject) 114 } 115 116 // 使用自定义封装的Promise 117 const customP = new Promise((resolve,reject)=>{ 118 // 模拟异步执行请求 119 // const xmlHttp = new XMLHttpRequest(); 120 // xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true); 121 // xmlHttp.onload = ()=>{ 122 // if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { 123 // resolve('success') 124 // } else { 125 // reject('error') 126 // } 127 // } 128 // xmlHttp.send(); 129 130 // 同步执行 131 resolve('success') 132 // reject('error') 133 }) 134 135 console.log(customP, 'customP'); 136 customP.then((res)=>{ 137 console.log(res, 'resolve回调'); 138 return '第一次回调' 139 // return new Promise((resolve,reject)=>{ 140 // reject('错错错') 141 // }) 142 },(err)=>{ 143 console.error(err, 'reject回调'); 144 return '2121' 145 }).then(()=>{ 146 console.log('then里面输出'); 147 }).then().catch((err)=>{ 148 console.error(err, 'catch里的错误'); 149 }) 150 151 152

针对resolve中返回Promise对象时的内部执行顺序

总结

以上就是我们常用的Promise基础实现,在实现过程中对比了Promise和函数嵌套处理异步请求的优缺点,Promise仍存在缺点,但是的确方便很多,同时更清晰的理解到错误处理如何进行异常穿透的,也能帮助我们更规范的使用Promise以及快速定位问题所在。

作者:京东物流 孙琦

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

Promise从入门到拿Offer之手写Promise

1、Promise构造函数的实现Promise构造函数用来声明示例对象,需要传入一个执行器函数。其中包括resolve函数和reject函数,以及几个重要的属性:状态属性、结果属性和回调函数队列。构造函数的基本框架resolve函数用于异步处理成功后调用的函数。其中包括验证对象状态修改次数,修改promise实例对象状态,异步调用成功的回调函数

Promise和Observable的映射

前言1.promise解决了嵌套地狱的问题,Observable解决了promise只有一个结果,和不可以取消的问题。2.使用的是rxjs6版本。3.这篇文章是方便使用Observable的API替换Promise的API。正常用法promise.then(result{}).ca

JavaScript中Promise 使用、原理以及实现过程

1.什么是Promisepromise是目前JS异步编程的主流解决方案,遵循Promises/A方案。2.Promise原理简析(1)promise本身相当于一个状态机,拥有三种状态pendingfulfilledrejected一个promise对象初始化时

Promise的奇怪用法和自己实现一个Promise

原文链接: Promise的奇怪用法和自己实现一个Promise(https://my.oschina.net/ahaoboy/blog/4645165)使用Promise实现一个页面所有图片加载完毕的回调importReact,{useEffect}from"react";exportdefault()