开发者社区 > 博文 > Promise规范与原理解析
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

Promise规范与原理解析

  • jd****
  • 2023-11-21
  • IP归属:北京
  • 6840浏览

    摘要

    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的基本概念

    含义

    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 --- 结果数据改变
    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为实例上的方法,报错
    

    特点

    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里处理。

    流程图

    简单使用

    // 模拟一个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以及快速定位问题所在。


    文章数
    1
    阅读量
    171

    作者其他文章