翼度科技»论坛 编程开发 JavaScript 查看内容

Promise规范与原理解析

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
摘要

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

首先我们先来看一段代码:异步请求的层层嵌套
  1. function 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. fn1()
复制代码
或者我们可以将上面的代码优化为下面这样
  1. function fn1(params) {
  2.   console.log(`我是fn1,我在函数${params}中执行!!!`);
  3. }
  4.   
  5. function 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.   
  21. function 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.   
  38. 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 --- 结果数据改变
  1. const p1 = Promise.resolve(64)
  2. const p2 = Promise.reject('我错了')
  3. const p3 = Promise.then()
  4. const p4 = Promise.catch()
  5. // 状态改变PromiseState 结果改变PromiseResult
  6. console.log(new Promise(()=>{}), 'Promise');  // PromiseState='pending' PromiseResult=undefined
  7. console.log(p1,'p1');  // PromiseState='Fulfilled' PromiseResult=64
  8. console.log(p2,'p2');  // PromiseState="Rejected" PromiseResult='我错了'
  9. console.log(p3, 'p3'); // then为实例上的方法,报错
  10. console.log(p4, 'p4');  // catch为实例上的方法,报错
复制代码

特点


  • 错误信息清晰定位:可以在外层捕获异常信息(网络错误、语法错误都可以捕获),有“冒泡”性质,会一直向后传递,直到被捕获,所以在最后写一个catch就可以了
  • 链式调用:每一个then和catch都会返回一个新的Promise,把结果传递到下一个then/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里处理。
流程图


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

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


总结

以上就是我们常用的Promise基础实现,在实现过程中对比了Promise和函数嵌套处理异步请求的优缺点,Promise仍存在缺点,但是的确方便很多,同时更清晰的理解到错误处理如何进行异常穿透的,也能帮助我们更规范的使用Promise以及快速定位问题所在。
作者:京东物流 孙琦
来源:京东云开发者社区 自猿其说Tech 转载请注明来源

来源:https://www.cnblogs.com/jingdongkeji/p/17853279.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具