回调函数、回调地狱,异常函数 Promise / then 和 catch / async 与 await / try...catch 的应用

2,323 阅读10分钟

回调函数

把 函数1 通参数的形式传递给 函数2, 在函数2内部以形参方式调用,函数1就叫 函数2的回调函数 通常用到回调函数的场景都是在异步代码封装

  • 简单的回调函数

    把 函数A 通参数的形式传递给 函数B, 在函数B内部以形参方式调用,函数A就叫 函数B的回调函数

        function A () {
            console.log('函数A执行')
        }
        function B(cb) {
            console.log('函数B执行')
            cb()
        }
        B(A);  // A 函数作为实参 传入 b函数中   =>  先执行B函数 通过B函数内部调用A函数 
  • 封装 异步函数 1 => 一般都是网络请求
        function fun(f){
            const time = Math.ceil(Math.random()*3000);  
            /* 
            随机数 返回值作为倒计时器 时间 当时间到达0时 执行当前函数  当前函数执行完毕后 调用函数fu()  
            fu()再执行
            */
            setTimeout(() => {
                console.log('异步函数执行',time);
                f();
            },time)
            

        }

        fun(
            function fu(){   // 函数 fu() 在此时为实参 传入函数 fun() 中,在 fun() 内部调用
                console.log('异步函数执行完后再执行');  
            }
        )

image.png

  • 封装 异步函数 2

    封装一个形如网络请求的异步函数 , 通过随机数生成时间来判断来判断是否请求成功, 当请求大于某一时间,请求失败,反之则请求成功

        function fun(sb,cg){
            const time = Math.ceil(Math.random()*3000);  // 随机数 返回值作为倒计时器 时间 
            setTimeout(() => {
                // 当时间大于3500 时 判断请求失败
                if(time > 3500){
                    console.log('请求失败');
                    // 请求失败后调用sb函数
                    sb();
                }else{
                    console.log('请求成功');
                    // 请求成功 调用cg函数
                    cg();
                }
            },time);
        }


        fun(
            // 参数1
            () => {  
                console.log('请求失败,不能继续访问');
            },
            // 参数2
            () => {
                console.log('请求成功,可以继续访问');
            }
        )

image.png

回调地狱

 回调地狱并非一个 bug,而是一种代码格式,而这种代码格式不利于我们阅读 
 
 需求:在网络请求时,发出判断,如果时间大于请求时间,请求失败,不再请求,如果未超过请求时间,则请求成功,继续请求
 
        function fun(sb, cg) {
            const time = Math.ceil(Math.random() * 3000) + 2000;  // 随机数 返回值作为倒计时器 时间 
            setTimeout(() => {
                // 当时间大于3500 时 判断请求失败
                if (time > 3500) {
                    console.log('请求失败,时间:', time);

                    sb();
                } else {
                    console.log('请求成功,时间:', time);
                    // 请求成功 调用cg函数
                    cg();
                }
            }, time)


        }

        fun(
            // 参数1
            () => {
                console.log('第一次请求失败,不能继续访问');
            },
            // 参数2
            () => {
                console.log('第一次请求成功,可以继续访问');
                // 第一次请求成功  发出第二次请求
                fun(
                    () => {
                        console.log('第二次请求失败,不能继续访问');
                    },
                    () => {
                        console.log('第二次请求成功,可以继续访问');
                        // 第二次请求成功  发出第三次请求
                        fun(
                            () => {
                                console.log('第三次请求失败,不能继续访问');
                            },
                            () => {
                                console.log('第三次请求成功,可以继续访问');
                            }
                        )
                    }
                )
            }
        )

第一次访问成功,继续下一次,到达第二次以后,访问失败,不再继续访问 image.png

Promise

Promise (契约) 是解决回调地狱的一种方法

Promise 的三个状态 1. 持续: pending 2. 成功: fulfilled 3. 失败: rejected

  • resolve 会把我们这个 promise 状态转换为 成功
  • reject 会把我们这个 promise 状态转换为 失败

一个 Promise 是对一个异步操作的封装,异步操作有 等待成功 、成功和失败三个状态 ,对应了Promise的三个状态

promise 只会发生两个转换 (一旦转换完成就不能再改变) 1. 持续 ==> 成功 2. 持续 ==> 失败

    const P = new Promise( function(reslove,reject) {
            // resolve 会把我们这个 promise 状态转换为 成功
            // reject 会把我们这个 promise 状态转换为 失败   两个都是函数
            const time = Math.ceil(Math.random() * 3000) + 2000;  // 随机数 返回值作为倒计时器 时间 
            setTimeout(() => {
                // 当时间大于3500 时 判断请求失败
                if (time > 3500) {
                    console.log('请求失败,时间:', time);

                    reject('失败!不能再继续访问')
                } else {
                    console.log('请求成功,时间:', time);
                    // 请求成功 调用cg函数
                    reslove('成功!可以继续访问');
                }
            }, time)
        })

        console.log(P);

image.png

then / catch

  1. then 在Promise 成功时触发,并且接受 resolve 时传递的参数
  2. catch 在Promise 失败时触发,并且会接受 reject 时传递的参数
        const P = new Promise( function(reslove,reject) {
            // resolve 会把我们这个 promise 状态转换为 成功
            // reject 会把我们这个 promise 状态转换为 失败   两个都是函数
            const time = Math.ceil(Math.random() * 3000) + 2000;  // 随机数 返回值作为倒计时器 时间 
            setTimeout(() => {
                // 当时间大于3500 时 判断请求失败
                if (time > 3500) {
                    console.log('请求失败,时间:', time);

                    reject('失败!不能再继续访问')
                } else {
                    console.log('请求成功,时间:', time);
                    // 请求成功 调用cg函数
                    reslove('成功!可以继续访问');
                }
            }, time)
        })

        P.then(function(res){
            console.log('000',res);
        }).catch(function(res){
            console.log('111',res);
        })

image.png

  • 封装 Promise
        function fun(){
       
            const P = new Promise( function(reslove,reject) {
                // resolve 会把我们这个 promise 状态转换为 成功
                // reject 会把我们这个 promise 状态转换为 失败   两个都是函数
                const time = Math.ceil(Math.random() * 3000) + 2000;  // 随机数 返回值作为倒计时器 时间 
                setTimeout(() => {
                    // 当时间大于3500 时 判断请求失败
                    if (time > 3500) {
                        reject('失败!不能再继续访问')
                    } else {
                        // 请求成功 调用cg函数
                        reslove('成功!可以继续访问');
                    }
                }, time)
            })
            return P;  // 将 P 作为返回值 返回
        }

        fun().then((res) => {
            console.log('第一次请求成功,可继续访问');
        }).then((res) => {
            console.log('第二次请求成功,可继续访问');
        }).then((res) => {
            console.log('第三次请求成功,可继续访问');
        }).then((res) => {
            console.log('第四次请求成功,可继续访问');
        }).then((res) => {
            console.log('第五次请求成功,可继续访问');
        }).catch((res) => {
            console.log('请求失败,不能继续访问');
        })

image.png

async / await

作用: 能帮助我们把 异步代码, 写的和 同步代码一样
        function fun() {
            const P = new Promise(function (reslove, reject) {
                // resolve 会把我们这个 promise 状态转换为 成功
                // reject 会把我们这个 promise 状态转换为 失败   两个都是函数
                const time = Math.ceil(Math.random() * 3000) + 2000;  // 随机数 返回值作为倒计时器 时间 
                setTimeout(() => {
                    // 当时间大于3500 时 判断请求失败
                    if (time > 3500) {
                        reject('失败!不能再继续访问')
                    } else {
                        // 请求成功 调用cg函数
                        reslove('成功!可以继续访问');
                    }
                }, time)
            })
            return P;  // 将 P 作为返回值 返回
        }



        // 函数开头必须书写 async 表明内部可以书写 await
        async function newFun() {

            let p1 = await fun();
            console.log(p1);
            // 如果请求失败就执行 此行代码
            console.log('失败!');
        }
        newFun();  // 调用async 声明的函数

await 后边需要跟着 promise,await 表示等到的意思, 执行到 fun() 虽然是异步的,但是因为有 await 关键字, 此时不会往下继续执行,而是等待 fun() 执行完毕, 在往下执行

image.png

  • async/await 的问题
    1. 没有办法捕获到 错误, 只能接受 promise 的成功状态
    2. 如果报错, 会中断程序执行 image.png
  • async/await 怎么抛出错误异常 ?

如果可能出错的代码比较少的时候可以使用try/catch结构来了处理,如果可能出错的代码比较多的时候,可以利 用async函数返回一个promise对象的原理来处理,给async修饰的函数调用后返回的promise对象,调用catch方 法来处理异常。

  • async/await 的问题 解决方法 1 ( try...catch )
    • try...catch

      首次执行的时候 会走 try 这个分支, 如果这个位置有报错,他会结束执行 try 分支, 然后走 catch 分支,如果再运行 try 分支的时候, 没有报错, 那么 catch 不会运行

        function fun() {

            const P = new Promise(function (reslove, reject) {
                // resolve 会把我们这个 promise 状态转换为 成功
                // reject 会把我们这个 promise 状态转换为 失败   两个都是函数
                const time = Math.ceil(Math.random() * 3000) + 3000;  // 随机数 返回值作为倒计时器 时间 
                setTimeout(() => {
                    // 当时间大于3500 时 判断请求失败
                    if (time > 3500) {
                        reject('失败!不能再继续访问')
                    } else {
                        // 请求成功 调用cg函数
                        reslove('成功!可以继续访问');
                    }
                }, time)
            })
            return P;  // 将 P 作为返回值 返回
        }

        
        async function newFun() {
            try{ // 如果请求成功  执行此分支
                let p1 = await fun();
                console.log(p1);
            }catch(error){ 
                // 请求失败 执行此分支 
                console.log(error);
                console.log('请求失败');
            }
        }
        newFun();  // 调用async 声明的函数

image.png

  • async/await 的问题 解决方法 2

    • 解决方法2: 更改 promise 的封装

      原因: promise 执行 reject 时 async await 不能捕获到错误,

      解决: 让这个 promise 不管什么情况 都返回 resolve

      我们通过 返回的 参数, 区分现在是成功还是失败,开发中 对象内的 code 如果为0, 一般代表失败,对象内的 code 如果为1, 一般代表成功

        function fun() {

            const P = new Promise(function (reslove, reject) {
                // resolve 会把我们这个 promise 状态转换为 成功
                // reject 会把我们这个 promise 状态转换为 失败   两个都是函数
                const time = Math.ceil(Math.random() * 3000) + 3000;  // 随机数 返回值作为倒计时器 时间 
                setTimeout(() => {
                    // 当时间大于3500 时 判断请求失败
                    if (time > 3500) {
                        reslove({
                            code:0,
                            msg:'请求失败,不能继续访问'
                        })
                    } else {
                        // 请求成功 调用cg函数
                        reslove({
                            code:1,
                            msg:'请求成功,可以继续访问'
                        });
                    }
                }, time)
            })
            return P;  // 将 P 作为返回值 返回
        }

        async function newFun(){
            const p1 = await fun();
            // console.log(p1.code);
            if(p1.code === 0){   // 如果code 为0  请求失败
                console.log('您的网络有问题');
            }else{    // 如果code 为1  请求成功   输出 msg 对应的值 
                console.log(p1.msg);
            }
           
        }
        newFun(); 
        

Promise 的方法

  function fn() {
            const p = new Promise(function (reslove, reject) {
                const timer = Math.ceil(Math.random() * 3000) + 2000;
                setTimeout(() => {
                    if (timer > 3500) {
                        reject('失败');
                    } else {
                        reslove('成功');
                    }
                }, timer);

            })
            return p;
        }
  • Promise 实例对象的方法 finally => 不管是成功 还是失败都会执行
        fun().then(res => {
            console.log('成功');
        }).catch(res => {
            console.log('失败');
        }).finally(res => {
            console.log('不管成功还是失败都执行');
        })

image.png

  • Promis 构造函数上的方法
    1. all 接受一个数组, 数组内可以传递多个 promise 对象
    2. all方法的状态取决 数组内部的 promise对象的状态
    • 如果都是成功, all方法也是成功
    • 如果有一个失败, all方法就是失败
        Promise.all([fun(),fun(),fun()]).then(res => {
            console.log('全部成功,才会执行');
        }).catch(res => {
            console.log('有一个执行失败就会执行');
        })

image.png

  • race 方法
    • race 接受一个数组, 数组内可以传递多个 promise 对象
    • race 方法的状态取决于 数组内部的 promise 对象中第一个结束的
    • 如果第一个结束时是成功, race 就是成功
    • 如果第一个结束时时失败, race 就是失败
        Promise.race([fun(), fun(), fun()])
            .then(res => {
                console.log('如果我执行, 代表 数组内第一个结束的 promise状态为成功')
            })
            .catch(res => {
                console.log('如果我执行, 代表 数组内第一个结束的 promise状态为失败')
        })
        
        // then 执行 代表 第一个结束的 Promise 执行成功
        // catch 执行 代表 第一个结束的 Promise 执行失败

image.png

  • allSettled 方法
    • allSettled 接受一个数组, 数组内可以传递多个 promise
    • 他只会执行 .then, 接收到的参数是一个 数组形式的, 内部元素为 对应的 promise 的状态
        Promise.allSettled([fun(), fun(), fun(), fun()])
            .then(res => {
                console.log(res)
        })

image.png

  • 强行成功 / 失败

    1. 强行帮我们返回一个 成功状态的 promise 对象, 即 不管返回状态是成功还是失败 都会返回成功状态
        Promise.resolve()   // 4. 强行帮我们返回一个成功状态的 promise 对象
            .then(res => {
                console.log('成功')
            }).catch(res => {
                console.log('失败')
            })

image.png

  1. 强行帮我们返回一个 失败状态的 promise 对象, 即 不管返回状态是成功还是失败 都会返回失败状态
        Promise.reject()    // 5. 强行帮我们返回一个失败状态的 promise 对象
            .then(res => {
                console.log('成功')
            }).catch(res => {
                console.log('失败')
        })

image.png