• ES6 Promise详解


    前言

    本文主要是对Promise本身的用法做一个全面解析而非它的原理实现,如果你对Promise的用法还不是很熟悉或者想加深你对Promise的理解,我相信这篇文章一定会帮到你。

    首先让我们先了解一下JavaScript为什么会引入Promise

    回调地狱

    让我们先看这样一段代码,JQuery中ajax请求:

      $.ajax({
          url: "url1",
          data: {},
          success(res1) {
            //获取到第一个数据
            console.log(res1);
            //根据第一个数去去获取第二个数据
            $.ajax({
              url: "url2",
              data: {
                query: res1.xxx,
              },
              success(res2) {
                //获取到第二个数据
                console.log(res2);
                //根据第二个数去去获取第三个数据
                $.ajax({
                  url: "url3",
                  data: {
                    query: res2.xxx,
                  },
                  success(res3) {
                    //获取到第三个数据
                    console.log(res3);
                    //...
                  },
                });
              },
            });
          },
          error(err) {
            console.log(err);
          },
        });
    

    我们会发现这些回调一层又一层,这就被称为回调地狱(callback hell),尤其业务逻辑复杂的时候这些回调就会变得难以维护。于是Promise就出现了。我们再看一个使用promise封装的axios请求:

     axios
          .get(url1, {})
          .then((res1) => {
            //获取到第一个数据
            console.log(res1);
            //根据第一个数去去获取第二个数据
            return axios.get(url2, { query: res1.xxx });
          })
          .then((res2) => {
            //获取到第一个数据
            console.log(res2);
            //根据第二个数去去获取第三个数据
            return axios.get(url3, { query: res2.xxx });
          })
          .then((res3) => {
            //获取到第三个数据
            console.log(res3);
            //...
          })
          .catch((err) => {
            console.log(err);
          });
    
    

    通过对比我们会发现Promise解决了传统的回调函数的回调地狱问题,使得业务逻辑显得更加清晰了。接下来我们就开始正式介绍Promise了。

    概述

    Promise是现代异步编程的基础,在Promise返回给我们的时候操作其实还没有完成,但Promise对象可以让我们操作最终完成时对其进行处理,无论成功还是失败。

    Promise的返回有三种状态分别是等待(pending), 成功(fulfilled),拒绝(rejected),我们看以下示例(后续我们将用延时器setTimeout来代表我们的异步操作)

     const promise1 = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(1);
          },1000);
        });
        console.log(promise1);
    

    此时我们可以看到我们获取的Promise是pending(等待的状态)。

    1658637004450.jpg

    同样当我们一秒钟过后再去获取Promise

     const promise1 = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(1);
          }, 1000);
        });
        setTimeout(() => {
          console.log(promise1);
        }, 1000);
    

    它得到的就是成功(fulfilled)状态

    1658637292640.jpg

    然后我们将resolve换成reject

    const promise1 = new Promise((resolve, reject) => {
          setTimeout(() => {
            reject(1);
          }, 1000);
        });
        setTimeout(() => {
          console.log(promise1);
        }, 1000);
    

    它得到的便是拒绝(rejected)状态,同时给你抛出了一个错误

    1658637478041.jpg

    基本使用

    Promise构造函数只有一个函数作为参数,这个函数会在一个Promise被实例化出来后会被立即执行

     new Promise((resolve, reject) => {
          console.log(1);
        });
        console.log(2);
    

    此时输出的结果是:1 2

    Promise接收的函数有两个参数,分别是resolve和reject,其中resolve代表一切正常的时候所调用的函数,reject则代表我们程序异常的时候所调用的函数。resolve函数传入的参数用于向下一个then传递一个值,而reject函数传入的参数则会被.catch捕捉。而Promise.finally则是在Promise状态完成后触发的一个回调,即无论是resolve还是reject都会触发

        //成功示例
        new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve("成功的值");
          });
        })
          .then((res) => {
            console.log(res); //成功的值
          })
          .catch((err) => {
            //不会触发
            console.log(err);
          })
          .finally(() => {
            console.log("end"); //end
          });
    
        //失败示例
        new Promise((resolve, reject) => {
          setTimeout(() => {
            reject("失败的的值");
          });
        })
          .then((res) => {
            //不会触发
            console.log(res);
          })
          .catch((err) => {
            console.log(err); ////失败的值
          })
          .finally(() => {
            console.log("end"); //end
          });
    

    以上便是Promise的基本使用,但是只掌握它的基本使用可不行,我们还需要对其更深入的研究

    链式调用

    当我们使用Promise的时候,只要我们在.then的回调函数中返回一个成功状态(resolve)的Promise,则在下一个.then的回调函数中便可获取到这个成功函数(resolve)的参数,基于这个特性便有了Promise的链式调用。

        new Promise((resolve, reject) => {
          //这里一般会有一个网络请求或其它异步操作
            resolve("成功的值1");
        })
          .then((res) => {
            console.log(res); //成功的值1
            return new Promise((resolve, reject) => {
            //这里一般会有一个网络请求或其它异步操作
              resolve("成功的值2");
            });
          })
          .then((res) => {
            console.log(res); //成功的值2
            return new Promise((resolve, reject) => {
              //这里一般会有一个网络请求或其它异步操作
              resolve("成功的值3");
            });
          })
          .then((res) => {
            console.log(res); //成功的值3
            //以此类推...
          });
    
    

    我们可以对其进行简写,比如

        new Promise((resolve, reject) => {
          //这里一般会有一个网络请求或其它异步操作
          resolve("成功的值");
        });
    
    

    可以简写为

    Promise.resolve('成功的值')
    

    所以我们的链式调用可以简写为

        new Promise((resolve, reject) => {
          //这里一般会有一个网络请求或其它异步操作
          resolve("成功的值1");
        })
          .then((res) => {
            console.log(res); //成功的值1
            return Promise.resolve("成功的值2");
          })
          .then((res) => {
            console.log(res); //成功的值2
            return Promise.resolve("成功的值3");
          })
          .then((res) => {
            console.log(res); //成功的值3
            //以此类推...
          });
    

    同样的reject的简写方式也和resolve一样

        new Promise((resolve, reject) => {
          //这里一般会有一个网络请求或其它异步操作
          reject("失败的值");
        });
        
        //简写为
        Promise.reject('失败的值')
    
    

    一般我们在实际项目中一般会这样写

          ...
          //网络请求中获取到数据后
          if(xxx){
            //成功
            return Promise.resolve('请求的值')
          }
          return Promise.reject('失败原因')
          ...
    

    其实.then中也会自动返回Promise的封装,也就是说这个链式调用我们可以直接这样写

        new Promise((resolve, reject) => {
          //这里一般会有一个网络请求或其它异步操作
          resolve("成功的值1");
        })
          .then((res) => {
            console.log(res); //成功的值1
            return "成功的值2";
          })
          .then((res) => {
            console.log(res); //成功的值2
            return "成功的值3";
          })
          .then((res) => {
            console.log(res); //成功的值3
            //以此类推...
          });
    

    以上便是Promise的链式调用,Promise的链式调用一般用于这些步骤间有先后顺序的操作,比如开头举的例子,需要使用前一个接口请求的数据作为参数去请求另一个接口的情形。

    Promise中的all函数

    在实际项目中你是否遇到过这样一个情况:你有A、B、C三个接口(或则更多),C接口的参数需要用到A和B两个接口的结果值,此时你为怎么做?

    • 做法1

    先请求A接口再请求B接口最后再根据AB接口的结果去请求C接口

        new Promise((resolve, reject) => {
          //请求A接口,这里用setTimeout模拟请求
          setTimeout(() => {
            resolve("A的结果");
          }, 100);
        })
          .then((res) => {
            //根据A结果请求B接口
            setTimeout(() => {
              return "B的请求结果";
            }, 100);
          })
          .then((res) => {
            //根据A和B结果请求C接口
            setTimeout(() => {
              console.log("C的请求结果");
            }, 100);
          })
          .catch((err) => {
            //这里暂不做错误考虑
          });
    

    这种写法逻辑上是没问题的,但是B和A的请求之间是完全没有交集的,而浏览器的http请求是可以同时发起多个请求的,所以这种写法很明显增加了接口请求时间

    • 做法2

    在每个请求结束后都去调用请求C的函数,在这个函数中判断两个请求的数据是否都获取到了,然后再进行处理

        let isResultA = false;
        let isResultB = false;
    
        //请求A接口,这里用setTimeout模拟请求
        setTimeout(() => {
          isResultA = true;
          getC()
        }, 100);
    
        //请求B接口,这里用setTimeout模拟请求
        setTimeout(() => {
          isResultB = true;
          getC()
        }, 100);
        function getC() {
          if (isResultA && isResultB) {
            //根据A和B的结果请求C接口数据
            setTimeout(() => {
              console.log("C的请求结果");
            }, 100);
          }
        }
    

    很显然这种在写法上是很麻烦的,所以Promise提供了all方法

    • 做法3

    Promise.all接收一个iterable类型(Array,Map,Set 都属于 ES6 的 iterable 类型),可以放多个Promise实例,最后.then中获得的是这些输入的Promise的resolve回调的结果数组。同时只要任何一个输入的Promise的reject回调执行或者输入不合法的Promise就会立即抛出错误

        Promise.all([
          new Promise((resolve, reject) => {
            //请求A接口,这里用setTimeout模拟请求
            setTimeout(() => {
              resolve("A的结果");
            }, 2000);
          }),
          new Promise((resolve, reject) => {
            //请求B接口,这里用setTimeout模拟请求
            setTimeout(() => {
              resolve("B的结果");
            }, 1000);
          }),
        ])
          .then((res) => {
            console.log(res[0]); //A的结果
            console.log(res[1]); //B的结果
            //根据A和B的结果请求C接口数据
            setTimeout(() => {
              console.log("C的请求结果");
            }, 100);
          })
          .catch((err) => {
            console.log(err);
          });
    
    

    Promise中的race函数

    Promise.race方法返回一个promise,一旦迭代器中的某个promise完成,返回的promise就会被完成。简单来说就是它接收的promise实例中谁快就用谁的结果,不管你的结果是resove的还是reject

        Promise.race([
          new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("结果1");
            }, 1000);
          }),
          new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("结果2");
            }, 500);
          }),
          new Promise((resolve, reject) => {
            //请求B接口,这里用setTimeout模拟请求
            setTimeout(() => {
              reject("结果3");
            }, 100);
          }),
        ])
          .then((res) => {
            //不会触发
            console.log(res);
          })
          .catch((err) => {
            console.log(err); //结果3
          });
    

    上面示例很显然第三个Promise示例最先返回结果,所以Promise.race便使用了第三个Promise的结果

    Promise中的any函数

    Promise.any函数它也接收一个Promise实例的可迭代对象,只要其中的一个promise实例成功,就返回那个已经成功的promise,只有所有的promise实例都失败才会返回失败的(reject)的数组

        Promise.any([
          new Promise((resolve, reject) => {
            setTimeout(() => {
              reject("结果1");
            }, 1000);
          }),
          new Promise((resolve, reject) => {
            setTimeout(() => {
              reject("结果2");
            }, 500);
          }),
          new Promise((resolve, reject) => {
            //请求B接口,这里用setTimeout模拟请求
            setTimeout(() => {
              reject("结果3");
            }, 100);
          }),
        ])
          .then((res) => {
            //不会触发
            console.log(res);
          })
          .catch((err) => {
            console.log(err); //AggregateError: All promises were rejected
          });
    

    这个函数适用的场景可能不是很多,在这里我大概想到的一个场景就是:有三个接口A,B,C,这三个接口很不稳定但是它们返回的成功结果都一样,所以我们需要对这三个接口进行同时请求,只要它们其中有一个接口返回成功,那么我们便用这个接口的值。所以这三个接口只要有一个可用我们便可拿到想要的结果

    async和await

    async和await其实就是promise的语法糖形式,它可以让我们的异步代码包装成同步的形式理解。await顾名思义就是等待的意思,它必须使用在一个async的函数中,await后面跟的是一个实例化Promise,它返回的值则是这个Promise成功返回的 resolve 状态值。其实它的用法很简单,如下

          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("结果");
            }, 1000);
          });
        };
    
        const getData = async () => {
          const res = await promiseFun();
          console.log(res);//结果
        };
        getData();
    

    如果我们把文章开头的axios请求例子改为async,await的形式它将会是这个样子

        const getAxiosData = async () => {
          try {
            const res1 = await axios.get(url1, {});
            const res2 = await axios.get(url2, { query: res1.xxx });
            const res3 = await axios.get(url2, { query: res2.xxx });
            console.log(res3);
          } catch (err) {
            console.log(err);
          }
        };
        getAxiosData();
    
    

    此时的代码逻辑看起来就会清晰很多

    写在最后

    Promise的大致用法基本也就介绍完了,其实Promise还涉及到另一个方面的知识事件循环(Event Loop) 还有宏任务微任务等,由于篇幅原因,这部分我会抽时间单独写一篇关于这方面的文章。同时如果你发现文中有错误或不妥的地方欢迎指出,一定及时修改,感谢~

    创作不易,你的点赞就是我的动力!如果感觉这篇文章对你有所帮助的话就请点个赞吧,感谢orz

  • 相关阅读:
    阿里云Maven和Gradle仓库最新配置
    Hive实战练习(包含数据集)
    新媒体背景下大学生的志愿服务参与研究(lunwen+任务书)
    MySQL update 是锁行还是锁表?
    Vue思考题_01v-for与v-if的优先级谁更高
    【深入解读Redis系列】Redis系列(五):切片集群详解
    IIS通过ARR实现负载均衡
    善用 vscode 的多光标批量和模板等技巧来提效
    git使用(上传自己的项目到github上)
    【SwitchyOmega】SwitchyOmega 安装及使用
  • 原文地址:https://www.cnblogs.com/zdsdididi/p/16599344.html