对 promise 的深入理解

131 阅读5分钟

1. 基本概念

1) 作用: 一种新的异步代码封装方案, 用来代替 回调函数的

3种状态?
  1. 持续   pending
  2. 成功   fullfilled (resolved)
  3. 失败   rejected
  + 注意: 一个Promise的状态转换只有两种
    1. 持续 => 成功
    2. 持续 => 失败

 3) Promise 基本语法
     promise 是 JS 内置的一个构造函数 ,书写固定格式记住即可
  1. 创建promise
    语法: let p = new Promise(function(第一个形参,第二个形参){异步代码}) //resolve和reject都是函数
    - resolve  第一个形参: 内部的值是一个函数, 调用之后可以将当前这个 promise 的状态设置为 成功
    resolve(a)运行后,其实调用的是then(b=>{ })里的箭头函数b=>{ }, 箭头函数里形参数b其实就是resolve的实参a
    
    - reject   第二个形参: 内部的值是一个函数, 调用之后可以将当前这个 promise 的状态设置为 失败
    
    
     
 5) 状态转换时,触发的函数
    => then  方法: resolve()会将 promise 状态成功。然后运行then里面的代码
    => catch 方法: reject() 会将 promise 状态失败。状态转为失败, 触发catch函数,并执行里面的函数
    
 - 如果Promise对象状态为 成功 就运行then里的函数。 
 - 如果Promise对象状态为 失败 就运行catch里的函数
 
 promise 中的代码执行出现异常,则默认promise状态转为失败,而且代码不会抛出异常中断,而是将异常信息,传递给catch(形参)函数 的形参
 
  promise中的代码都是同步执行,但是then,catch注册的函数是异步执行, then\catch方法要等到 Promise对象改变状态后再执行
    <script>        
        const p = new Promise(function (resolve, reject) {//同步代码从上而下运行,p接收一个promise 对象
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')
           
            setTimeout(() => { // setTimeout是异步代码
                if (timer > 1500) {
                    console.log('买水失败, 用时: ', timer)
                    reject() //timer大于1500就将promise对象的状态 设置为失败,会触发catch函数并运行catch里的代码
                } else {
                    console.log('买水成功, 用时: ', timer)
                    resolve() //timer小于1500就将promise对象的状态 设置为成功,会触发then函数并运行then里的代码
                }
            }, timer)
        })

        console.log('打印 变量 p: ', p) //得到一个 promise 对象

        p.then(() => { //如果promise 对象p状态变为成功,就运行then内的代码
            console.log('如果我这行内容打印, 说明 promise 的状态为 成功')
        })
        p.catch(() => {//如果promise 对象p状态变为失败,就运行catch内的代码
            console.log('如果我这行内容打印, 说明 promise 的状态为 失败')
        })
    </script>

2. 将 promise 封装到函数中(了解即可)

第1版 多次调用封装了promise的函数

    缺点:无法确定是哪一次失败
    <script>
        // 将 promise 封装到函数中
        function fn() {
            const p = new Promise(function (resolve, reject) {
                const timer = Math.ceil(Math.random() * 3000)
                console.log('班长, 去帮我买瓶水')

                setTimeout(() => {
                    if (timer > 1500) {
                        reject('超时, 所以买水失败') //
                    } else {
                        resolve('没有超时, 买水成功') //
                    }
                }, timer)
            })
            return p
        }

        const res = fn() //第一次调用fn(),得到 fn 函数内部的 promise 对象,在对象的后面书写一个 then
        // 链式调用
        res.then((str) => { 
        //promise对象res状态变为成功后,将resolve('没有超时, 买水成功')函数的实参'没有超时, 买水成功'赋值给then
        中箭头函数的形参str
            console.log(`因为 ${str}, 所以奖励班长 10 个 bug`)
            return fn()
        }).then((str) => {
            console.log('如果我输出了, 表示班长 第二次买水成功')
            return fn()
        }).then((str) => {
            console.log('如果我输出了, 表示班长 第三次买水成功')
        }).catch((str) => {   // promise对象res状态变为失败后触发catch,运行里面的代码。缺点无法确定是哪一次失败了
            console.log(`如果我输出了, 说明之前某一次买水失败了`)
        })
    </script>

3. async 与 await

     asyncawait能够将 异步代码, 写的像 "同步代码一样"

     async: 书写在 一个函数的 开头, 表明当前函数是一个异步函数, 内部可以书写 await

     await: 具有等待的含义, 书写在 fn函数 前, 代码运行到这个位置的时候, 会有一个等待效果
             一直等到这个fn函数中的异步任务结束, 并且将异步任务的反馈结果 当一个值返回出来

<script>
    function fn() {
            const p = new Promise(function (resolve, reject) {  //同步代码从上而下运行,p接收一个promise 对象
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')

            setTimeout(() => { // setTimeout为异步代码,timer毫秒后再运行
                if (timer > 1000) {
                    reject('超时, 所以买水失败') // 控制台输出报错:Uncaught (in promise) 超时, 将promise对象设置为失败,
                } else {
                    resolve('没有超时, 买水成功') //timer<1000 将promsie对象状态设置为成功
                    console.log('第xx次买水: ', rx)
                }
            }, timer)
        })
        return p    //同步代码会先运行,返回 promise 对象p
    }

    newFn()
    async function newFn() {
        // 因为函数开头写了 async, 所以这个函数就是一个独特的异步函数, 内部可以书写 await

        // await 是等待的意思, 它必须等待后边fn()函数里的 promise对象p 状态改变后再往下继续执行代码
        const r1 = await fn()
        //第1次调用fn() 会返回一个promsie对象p,因为有await所以不会继续执行,而是等着promsie对象状态的改变       
        //timer毫秒后,如果p状态改为成功,r1就被赋值为'没有超时, 买水成功'
        //timer毫秒后,如果p状态改为失败,就会报错:Uncaught (in promise) 超时, 所以买水失败。不会继续执行下面的代码了  
        console.log('第一次买水: ', r1) 
        const r2 = await fn()//第2次调用fn() 同上
        console.log('第二次买水: ', r2)

        const r3 = await fn()//第3次调用fn() 同上
        console.log('第三次买水: ', r3)
    }

</script>

1. async 与 await 的缺点

不能正常的捕获到 promise 的失败状态
1)用try...catch方法 解决缺点
 <script>
        function fn() {//同步代码
            const p = new Promise(function (resolve, reject) {//同步代码从上而下运行,p接收一个promise 对象
                const timer = Math.ceil(Math.random() * 3000)
                console.log('班长, 去帮我买瓶水')

                setTimeout(() => { // setTimeout为异步代码,timer毫秒后再运行
                    if (timer > 1000) {
                        reject('超时, 所以买水失败') 
                        //将promise对象p的状态设置为失败, 被try-catch 捕获,所以 error='超时, 所以买水失败'
                    } else {
                        resolve('没有超时, 买水成功') 
                        //将promise对象p的状态设置为成功
                    }
                }, timer)
            })
            return p    //得到 fn 函数内部的 promise 对象
        }
        newFn()

        async function newFn() {
            try {
            //await先拿到fn()调用后返回的promise对象,然后等待promise对象 p状态的改变
            //p为成功 会把r1赋值为'没有超时, 买水成功'
            //p为失败 error='超时, 所以买水失败'
            
                const r1 = await fn()
                console.log('第一次买水: ', r1) //'没有超时, 买水成功'
            } catch (error) { // 如果发生报错, error会捕获resolve函数里的参数 '超时, 所以买水失败'
                console.log(error) //error='超时, 所以买水失败'
            }
            
            console.log('上层有try-catch,即使上层error报错,也可以继续向下执行,不会中断')
            
            try {
                const r1 = await fn() //p的状态设置为成功  将resolve('没有超时, 买水成功') 函数的实参,传给r1
                console.log('第二次买水: ', r1)  //'没有超时, 买水成功'
            } catch (error) {
                console.log(error)  //error='超时, 所以买水失败'
            }
        }
    </script>
2)封装一个永远不会失败的 promise 解决缺点
function fn() {//同步代码
    const p = new Promise(function (resolve, reject) {  //同步代码从上而下运行,p接收一个promise 对象
        const timer = Math.ceil(Math.random() * 3000)
        console.log('班长, 去帮我买瓶水')
        setTimeout(() => {
            if (timer > 1500) {
                // 因为reject会直接报错,代码就不运行了。不会像resolve一样继续执行代码
                resolve({  //timer大于1500,将promise对象p 的状态改为成功 
                    code: 0, // 代表当前请求失败
                    msg: '超时, 所以买水失败'
                })
            } else {
                resolve({  //timer小于1500,将promise对象p 的状态改为失败
                    code: 1,  // 代表当前请求成功
                    msg: '没有超时, 买水成功'
                })
            }
        }, timer)
    })
    return p
}
newFn() 
async function newFn() {
    const r1 = await fn() //将resolve() 函数的实参,传给r1
    if (r1.code === 0) {  //p状态为失败
        console.log('请求失败的补救措施')
    } else {  //p状态为成功
        console.log('请求成功, 正常执行代码即可')
    }
}

4. promise 的其他方法

1. promise 对象上的方法: then-catch-finally

正常业务场景中, 我们再发起一个请求的时候, 会将页面弹出一个遮罩层
然后再请求结束的时候 需要将这个遮罩层关闭
这个时候如果放在 then 中 那么会有一个 问题, 就是请求失败的时候不会触发then
所以我们一般不会放在 then 关闭遮罩层, 而是 放在 finally
    function fn() {
        const p = new Promise(function (resolve, reject) {
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')
            setTimeout(() => {
                if (timer > 1500) {
                    reject('超时, 所以买水失败')
                } else {
                    resolve('没有超时, 买水成功')
                }
            }, timer)
        })
        return p
    }

    const res = fn()
    res.then(() => {
        console.log('成功时执行')
    }).catch(() => {
        console.log('失败时执行')
    }).finally(() => {
        console.log('每一次都会执行 (不会考虑成功还是失败)')
    })

2. promise 构造函数上的一些方法

Promise.all
    function fn() {
        const p = new Promise(function (resolve, reject) {
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')
            setTimeout(() => {
                if (timer > 1500) {
                    reject('超时, 所以买水失败')
                } else {
                    resolve('没有超时, 买水成功')
                }
            }, timer)
        })
        return p
    }
    
    Promise.all([fn(), fn(), fn()]).then(() => {
        console.log('所有的 参数 全部都返回一个 成功状态的时候, 会执行')
    }).catch(() => {
        console.log('所有参数中, 有一个为失败状态, 就会执行 catch')
    })
Promise.race
    function fn() {
        const p = new Promise(function (resolve, reject) {
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')
            setTimeout(() => {
                if (timer > 1500) {
                    reject('超时, 所以买水失败')
                } else {
                    resolve('没有超时, 买水成功')
                }
            }, timer)
        })
        return p
    }
    
    Promise.race([fn(), fn(), fn()]).then(() => {
        console.log('这些参数中, 结束最快的那一个状态为 成功的时候执行')
    }).catch(() => {
        console.log('这些参数中, 结束最快的那一个状态为 失败的时候执行')
    })
Promise.allSettled
        function fn() {
            const p = new Promise(function (resolve, reject) {
                const timer = Math.ceil(Math.random() * 3000)
                console.log('班长, 去帮我买瓶水')
                setTimeout(() => {
                    if (timer > 1500) {
                        reject('超时, 所以买水失败')
                    } else {
                        resolve('没有超时, 买水成功')
                    }
                }, timer)
            })
            return p
        }

        Promise.allSettled([fn(), fn(), fn()]).then((res) => {
            /**
             *  在数组内传递的 promise 全都执行完毕后, 
             * 返回一个数组给到 then 函数
             *  数组内的对象就是我们传递进来的 promise 对象的执行结果
            */
            console.log(res)
        })

微信截图_20230212221207.png

Promise.resolve
    Promise.resolve().then(() => {
        console.log('强制返回一个 状态为 成功的 promise')
    })
Promise.reject
    Promise.reject().then(() => {
        console.log('如果我打印了, 说明 当前的 promise 状态为成功')
    }).catch(() => {
        console.log('强制返回一个 状态为 失败的 promise 对象')
    })