带你第一次认识promise

52 阅读7分钟
 /**
     *  回调函数
     * 
     *      当 函数A 作为 实参 传递到 函数B 中
     *      在 函数B 中通过 形参 调用 函数A
     * 
     *      此时我们就可以说 函数A 是一个 回调函数
    */
    
    
  /**
     *  回调地狱
     * 
     *      回调地狱的问题在于 书写完代码后, 不利于阅读与后续的维护
     *      并不是说 回调地狱会导致我们的功能无法实现
     * 
     * 
     *  所以为了解决回调地狱导致的代码不利于维护与阅读的问题, 出现了 promise
     * 
     *  promise 最重要的是解决了回调地狱, 而不是解决了 异步代码
     * 
     *  异步代码一直会用, 不管你用不用 promise
     * 
     * 
     *  利用 promise 书写的代码, 相对于 回调地狱, 更方便阅读与后续的维护
    */
    
    
      /**

认识 promise

     *  其实 promise 就可以理解为一个盒子, 这个盒子内部 可以帮我们承载一些 需要一定时间才能加载完毕的代码 (其实就是异步代码)
     * 
     * 
     *  刚书写一个 promise 内部的状态为 等待/持续
     *      将来promise 会转变状态, 要么转为成功
     *                              要么转为失败
     * 
     *      而且 promise 的状态一经转换, 永远不会在改变
     *              等待 -> 成功
     *              等待 -> 失败
     * 
     * 
     *  利用 ES6 新增的一个内置构造函数 帮助我们创建
    */



    const p = new Promise(function (reslove, rejected) {
        /**
         *  当前回调函数 会接收两个形参
         * 
         *  拼写并不重要
         * 
         *  第一个形参: 他的值是一个函数, 你可以调用, 调用完毕后, 可以修改 当前 promise 的状态为成功
         *  第二个形参: 他的值也是一个函数, 你也可以调用, 调用完毕后, 可以修改 当前 promise 的状态为失败
         * 
         *  注意: 两个形参都是一个函数, 函数由 Promise 处理, 我们不需要处理, 我们只需要在合适的时机 调用即可
        */

         rejected()      // 如果你这样写, 会让页面有一个报错, 以后我们会处理, 目前不用在意
        reslove()       // 将当前的 promise 状态修改为 成功
    })

    console.log(p)

      const p = new Promise(function (reslove, rejected) {
        const time = Math.floor(Math.random() * 3000) + 2000

        setTimeout(() => {
            if (time > 30) {
                // console.log('失败', time)
                rejected(time)
            } else {
                // console.log('成功', time)
                reslove(time)
            }
        }, time)
    })

    /**
     *  promise 实例化对象中, 有一个 方法 then 是一个函数
     *
     *      当前的 then 内部可以接收一个 回调函数
     *
     *      这个回调函数会在当前的 promise 实例化对象, 状态为成功的时候执行
     *
     *
     *  还有另外一个方法 catch 也是一个函数, 内部可以接受一个 回调函数
     *          这个回调函数会在当前的 promise 实例化对象, 状态为失败的时候执行
     *
     *
     *  注意:
     *      不管是 then 函数内部的 回调函数还是 catch 函数内部的回调函数
     *              都可以接受一个参数
     *      这个参数的值由在 promise 内部更改状态的函数 传递
     * 
     *      这个参数也不是必须要有, 你需要的话可以用, 不需要可以不写
    */

    p.then((res) => {
        console.log('当前的 promise 实例化对象 如果状态成功, 那么我会执行', res)
    }).catch((err) => {
        console.log('当前的 promise 实例化对象 如果状态失败, 那么我会执行', err)
    })
    

链式调用

      function fn() {
        return new Promise(function (reslove, rejected) {
            const time = Math.floor(Math.random() * 3000) + 2000
            setTimeout(() => {
                if (time > 3000000) {
                    rejected(time)
                } else {
                    reslove(time)
                }
            }, time)
        })
    }



    const p = fn()


    // 链式调用, 在一个 then 后 返回一个 新的 promise 实例化对象, 那么我们可以再添加一个 then 函数
    p.then((res) => {
        console.log('成功', res)
        return fn()
    }).then((res) => {
        console.log('如果第二次成功, 那么我也会执行')

        return fn()
    }).then((res) => {
        console.log('如果第三次成功, 那么我也会执行')
    }).catch((err) => {
        console.log('失败', err)
    })

Promise.all()

  // Promise.all(); 将所有Promise放在一个数组中,然后使用all调用,在then中就会把所有Promise的resolve返回的结果放在数组参数中返回
  // Promise.all会同时将所有的Promise全部执行完成,不是一个完成后再执行下一个
  // Promise.all 会根据数组中每个Promise在数组中位置放置resolve传入的参数结果
    function loadImage(src) {
      return new Promise(function (resolve, reject) {
        var img = new Image();
        img.src = src;
        img.onload = function () {
          resolve(img);
        };
      });
    }

    var arr=[];
    for(var i=2;i<6;i++){
      arr.push(loadImage(`./img/img_${i}.jpeg`))
    }

   //   当数组里的所有Promise都执行resolve,说明每个图片都加载完成了
   // 这时候执行then,并且在then中参数list中就会存储了每个resolve返回的参数的结果组成的数组
     Promise.all(arr).then(function(list){
        list.forEach(function(item){
           console.log(item.src)
        })
     })

Promise.allSettled()

      如果Promise中有一个没有执行resolve,执行的是reject,那么就不能使用Promise.all
      需要使用allSettled
      当多个Promise需要同时调用时,如果不确定是否都能成功,使用allSettled
        Promise.allSettled();
      function loadImage(src) {
        return new Promise(function (resolve, reject) {
          var img = new Image();
          img.src = src;
          img.onload = function () {
            resolve(img);
          };
          img.onerror=function(){
            reject();
          }
        });
      }

  var arr=[loadImage("./img/img_2.jpeg"),loadImage("./img/img_3.jpeg"),loadImage("./img/img_4.jpeg"),loadImage("./img/img_5.jpeg"),loadImage("./img/img_6.jpeg")];
  Promise.all(arr).then(function(list){
    console.log(list)
  }).catch(function(){
    console.log("aa")
  })
    Promise.allSettled(arr).then(function(list){
        console.log(list);
        // [{status: 'fulfilled', value: img},{status: 'rejected', reason: undefined}]
        // status是fulfilled表示成功,value就是成功后resolve传入的参数值
        // status是rejected表示失败,reason就是成功后reject传入的参数值
    })

Promise.race();

 //   赛跑  当多个Promise同时调用时,谁的速度最快完成resolve这里,then返回对应resolve的结果
    Promise.race();
  
  var arr=[loadImage("./img/img_1.jpeg"),loadImage("./img/img_11.jpeg"),loadImage("./img/img_12.jpeg"),loadImage("./img/img_13.jpeg"),loadImage("./img/img_6.jpeg")];
        Promise.race(arr).then(function(img){
            console.log(img);
        }).catch(function(list){
            console.log(list)
        })

Promise.any();

  如果有一个成功的,就会把这个成功返回,如果都失败,打印全部失败
  var arr=[loadImage("./img/img_2.jpeg"),loadImage("./img/img_3.jpeg"),loadImage("./img/img_4.jpeg"),loadImage("./img/img_5.jpeg"),loadImage("./img/img_6.jpeg")];
        Promise.any(arr).then(function(img){
            console.log(img);
        })

var arr=[loadImage("./img/img_1.jpeg"),loadImage("./img/img_2.jpeg"),loadImage("./img/img_12.jpeg"),loadImage("./img/img_13.jpeg"),loadImage("./img/img_6.jpeg")];
        Promise.any(arr).then(function(img){
            console.log(img);
        }).catch(function(list){
            console.log(list)  (全部失败才会执行,必须有失败状态的代码)
        })

* async 和 await

     * 
     *      必须要结合 promise 一起使用
     * 
     *      async 书写在一个函数的前边, 表明当前是一个异步函数
     * 
     *      await 书写在 promise 实例化对象前, 有一个作用是 只有当前这个对象的状态确定, 才会往下一行运行
     * 
     * 
     * 
     *      注意:
     *          async await 只能处理 promise 的成功状态, 不能处理失败状态
    */

            function post() {
                return new Promise(function (reslove, rejected) {
                    const time = Math.floor(Math.random() * 3000) + 2000
                    setTimeout(() => {
                        if (time > 30) {
                            rejected(time)
                        } else {
                            reslove(time)
                        }
                    }, time)
                })
            }




            // 利用 async await 优化
            async function fn () {
                const res = await post()
                console.log('第一次请求完毕的结果: ', res)

                const res_2 = await post()
                console.log('第二次请求完毕的结果: ', res_2)

                console.log('一定是上边的异步代码运行完毕后, 才会执行我这个代码')
            }

            fn()
            
            

利用 async await 缺点更改

     *  async await 只能处理 promise 的成功状态, 不能处理失败状态
     */

    function post() {
        return new Promise(function (reslove, rejected) {
            const time = Math.floor(Math.random() * 3000) + 2000
            setTimeout(() => {
                if (time > 3000) {
                    rejected('当前请求超时, 目前的时间是' + time)
                } else {
                    reslove(time)
                }
            }, time)
        })
    }

    async function fn() {

        // 当前写法 不能处理 promise 的错误状态
        // const res = await post()
        // console.log('第一次请求完毕的结果: ', res)

        /**
         *  解决方案
         *      推荐做法
        */
        try {
            // 如果当前分支的代码, 运行完毕没有报错, 那么直接结束
            const res = await post()
            console.log('第一次请求完毕的结果: ', res)

            // 如果当前分支的代码运行完毕后, 有报错, 那么会阻断报错, 并且将错误信息, 传递给 catch 分支的形参
        } catch (err) {
            console.log(err, '如果我执行, 说明 第一次请求出现报错')
        }


        try {
            // 如果当前分支的代码, 运行完毕没有报错, 那么直接结束
            const res = await post()
            console.log('第二次请求完毕的结果: ', res)

            // 如果当前分支的代码运行完毕后, 有报错, 那么会阻断报错, 并且将错误信息, 传递给 catch 分支的形参
        } catch (err) {
            console.log(err, '如果我执行, 说明 第二次请求出现报错')
        }
    }

    fn()
    
    
    
    
    

async await 缺点优化2

  /**
     *  async await 只能处理 promise 的成功状态, 不能处理失败状态
     * 
     * 
     *  我们 async await 解决不了 promise 的失败状态
     * 
     *  所以我们干脆将 promise 修改成 一定是成功状态
     * 
     * 
     *  但是如果将 promise 修改成一定成功, 也会出现一个新的问题
     * 
     *  我们在外部没有办法确定当前是成功还是失败
     * 
     * 
     *      所以有一个 前端内部所有人的约定
     *          我们在返回信息的时候, 通过一个对象返回
     *              对象内部可以有多个属性, 其中有一个 属性叫做 code
     * 
     *              如果 code === 0 代表失败
     *                  code === 1  代表成功
    */

                function post() {
                    return new Promise(function (reslove, rejected) {
                        const time = Math.floor(Math.random() * 3000) + 2000
                        setTimeout(() => {
                            if (time > 3000) {
                                // rejected('当前请求超时, 目前的时间是' + time)
                                reslove({
                                    code: 0,
                                    data: '当前请求超时, 目前的时间是' + time
                                })
                            } else {
                                reslove({
                                    code: 1,
                                    data: time
                                })
                            }
                        }, time)
                    })
                }

            async function fn() {
                const res = await post()
                console.log('请求完毕的结果: ', res)

                if (res.code === 0) {
                    console.log('当前的请求失败了, 做一点措施, 并且不要再往下进行了')

                    return alert(res.data)
                }

                console.log('当前请求成功, 拿到数据后我们开始渲染页面')
            }

            fn()