Promise和async/await用法总结

Promise知识点汇总

Promise链式写法

定义2个函数,返回Promise:

    /**
     * 延迟millisecond毫秒后输出
     * @param millisecond
     * @return {Promise<unknown>}
     */
    setDelay = millisecond => {
        return new Promise((resolve, reject) => {
            if (typeof millisecond != 'number') {
                reject(new Error('参数必须是number类型'));
            }
            setTimeout(() => {
                resolve(`我延迟了${millisecond}毫秒后输出的,是第1个函数setDelay`)
            }, millisecond)
        })
    }

    /**
     * 延迟seconds秒后输出
     * @param seconds
     * @return {Promise<unknown>}
     */
    setDelaySecond = seconds => {
        return new Promise((resolve, reject) => {
            if (typeof seconds != 'number' || seconds > 10) {
                reject(new Error('参数必须是number类型,并且小于等于10'));
            }
            setTimeout(() => {
                resolve(`我延迟了${seconds}秒后输出的,是第2个函数setDelaySecond`)
            }, seconds * 1000)
        })
    }

Promise链式写法:

  setTimeout(this.setDelay(2000)
            .then((result) => {
                console.log(result);
                console.log(`显示第1个函数setDelay的result:${result}`);
                // return Promise.resolve(3);
                return this.setDelaySecond(3);
            })
            .then((result) => {
                console.log('我进行到第二步的');
                console.log(result);
                console.log(`显示第2个函数setDelaySecond的result:${result}`);
            }).catch((err) => {
                console.log(err);
            }), 5000)

catch和then捕获Promise异常

then式链式写法的本质其实是一直往下传递返回一个新的Promise,也就是说then在下一步接收的是上一步返回的Promise.

thencatch前:错误进入then的第2个回调函数,不进入catch

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('我进行到第一步的');
  return setDelaySecond(20)
})
.then((result)=>{
  console.log('我进行到第二步的');
  console.log(result);
}, (_err)=> {
  console.log('我出错啦,进到这里捕获错误,但是不经过catch了');
})
.then((result)=>{
  console.log('我还是继续执行的!!!!')
})
.catch((err)=>{
  console.log(err);
})

catchthen前:错误在catch捕获,不进入then的第2个回调函数。

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('我进行到第一步的');
  return setDelaySecond(20)
})
.catch((err)=>{ // 挪上去了
  console.log(err); // 这里catch到上一个返回Promise的错误
})
.then((result)=>{
  console.log('我进行到第二步的');
  console.log(result);
}, (_err)=> {
  console.log('我出错啦,但是由于catch在我前面,所以错误早就被捕获了,我这没有错误了');
})
.then((result)=>{
  console.log('我还是继续执行的!!!!')
})

总结:

  • catch写法是针对于整个链式写法的错误捕获的,而then第二个参数是针对于上一个返回Promise的。
  • 两者的优先级:就是看谁在链式写法的前面,在前面的先捕获到错误,后面就没有错误可以捕获了,链式前面的优先级大,而且两者都不是break, 可以继续执行后续操作不受影响。

需要注意的是catch不是链式的终点,如果catch后还有then,整个链式表达式还会继续执行下去,简而言之catch只是捕获链式表达式的错误,不是break


主动跳出Promise链式

使用reject可以主动跳出链式,但是后面的catch中的代码还是会继续执行:

this.setDelay(2000)
            .then((result) => {
                console.log(result)
                console.log('我进行到第一步的');
                return this.setDelaySecond(1)
            })
            .then((result) => {
                console.log('我进行到第二步的');
                console.log(result);
                console.log('我主动跳出循环了');
                return Promise.reject('跳出循环的信息') // 这里返回一个reject,主动跳出循环了
            })
            .then((result) => {
                console.log(result);
                console.log('我不执行');
            })
            .catch((mes) => {
                console.dir(mes);  // 跳出循环的信息
                console.log('我继续执行');
            })

执行顺序:

  • 进入第1个then,执行setDelaySecond
  • 进入第2个then,执行Promise.reject()
  • 跳过第3个then
  • 执行catch中的代码

一般来说,通过添加标识位来控制是否跳出Promise链式:

return isNotErrorExpection ? 
Promise.reject('跳出循环的信息') : Promise.resolve('继续执行');

Promise.all

Promise.all参数是Promise数组,输出是resolve数组,注意Promise.all中的Promise是并行执行的,都执行完后把resolve的值保存在数组中输出。

注意Promise.all总耗时是长的Promise执行时间。

Promise.all([this.setDelay(3000), this.setDelaySecond(1)])
.then(result => {
            // 输出[
            // "我延迟了3000毫秒后输出的,是第1个函数setDelay",
            // "我延迟了1秒后输出的,是第2个函数setDelaySecond"
            // ]
            console.log(result);
        }).catch(err => {
            console.log(err);
        });

async/await

async

async必须声明的是一个functionasync必须紧跟function

// 正确
async function process() {
}

// 错误
const async demo = function () {} 

await就必须是在这个async声明的函数内部使用,必须是第1层区域。

let data = 'data'
demo  = async function () {
    const test = function () {
        await data  // 在第2层,错误
    }
}

async的本质就是Promise,下面两种写法一样,demoPromise,通过then表达式拿到Promiseresolve值。

方法1:

// async本质是Promise
  async show() {
        return ('我是Promise');
    }

方法2:

// async本质是Promise
  async show() {
        // 写法2
         return new Promise((resolve, reject) => {
             resolve('我是Promise')
         });
    }

通过then表达式拿到Promise值:

  const demo = this.show();
        console.log(demo); // Promise {<fulfilled>: "我是Promise"}
        demo.then(result => {
            console.log(result);  // 通过then拿到resolve值
        })

总结:像对待Promise一样去对待async的返回值,通过then表达式拿到具体的返回值。


await错误用法

await顾名思义就是等待一会,只要await声明的函数还没有返回,那么下面的程序是不会去执行的。需要注意的是,await等待的是Promise的异步返回!!!看如下代码:

      handleClick = () => {
        this.demo().then(result => {
            console.log('输出', result); // 输出 我延迟了1秒
        });
    }

    //  demo的返回值是Promise,推荐带上返回值
    demo = async () => {
        let result = await setTimeout(() => {
            console.log('我延迟了一秒');
        }, 1000);
        console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
        return result;
    }
   this.demo().then(result => {
            console.log('输出', result); // 输出 我延迟了1秒
        });

执行结果为:在这里插入图片描述

可以看到await没有对setTimeout这个异步方法起作用。


await正确用法

await是在等待一个Promise的异步返回。

    handleClick = () => {
        this.demo().then(result => {
            console.log('输出', result); //
        });
    }

    //  demo的返回值是Promise,推荐带上返回值
    demo = async () => {
        let result = await new Promise((resolve, reject) => {
            resolve('我是声明值');
        });
        console.log(result);
        console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
        return result;
    }

输出结果:可以看到awaitPromise执行完再执行下一步,注意then的执行总是最后的。
在这里插入图片描述


使用async/await 避免Promise链式写法

目标:顺序执行,先延时1秒,在延迟2秒,再延时1秒,最后输出“完成”。
之前2个延时函数:

  /**
     * 延迟millisecond毫秒后输出
     * @param millisecond
     * @return {Promise<unknown>}
     */
    setDelay = millisecond => {
        return new Promise((resolve, reject) => {
            if (typeof millisecond != 'number') {
                reject(new Error('参数必须是number类型'));
            }
            setTimeout(() => {
                resolve(`我延迟了${millisecond}毫秒后输出的,是第1个函数setDelay`)
            }, millisecond)
        })
    }

    /**
     * 延迟seconds秒后输出
     * @param seconds
     * @return {Promise<unknown>}
     */
    setDelaySecond = seconds => {
        return new Promise((resolve, reject) => {
            if (typeof seconds != 'number' || seconds > 10) {
                reject(new Error('参数必须是number类型,并且小于等于10'));
            }
            setTimeout(() => {
                resolve(`我延迟了${seconds}秒后输出的,是第2个函数setDelaySecond`)
            }, seconds * 1000)
        })
    }

使用Promise实现,代码如下:通过button触发。

    handleClick = () => {
        this.setDelay(1000).then(result => {
            console.log(result);
            return this.setDelaySecond(2);
        }).then(result => {
            console.log(result);
            return this.setDelay(1000)
        }).then(result => {
            console.log(result);
            console.log('完成');
        }).catch(err => {
            console.log(err);
        })
    }

使用async/await来实现同样的效果:

  // 使用async/await循环执行
    handleClick = () => {
        this.demo().then(result => {
            console.log(result);
        });
    }

    demo = async () => {
        const result = await this.setDelay(1000);
        console.log(result);
        console.log(await this.setDelaySecond(2));
        console.log(await this.setDelay(1000));
        return ('执行完成');
    }

执行效果:
在这里插入图片描述

async/await的中断

Promise本身是无法中止的,Promise本身只是一个状态机,存储三个状态(pendingresolvedrejected),一旦发出请求了,必须闭环,无法取消

不同于Promise的链式写法,在async/await中想要中断程序就很简单了,想要中断的时候,直接return一个值就行,null,空,false都是可以的。

   // 使用async/await中断
    handleClick = () => {
        this.demo().then(result => {
            console.log(result);  // 中断
        });
    }

    demo = async () => {
        let count = 6;
        const result = await this.setDelay(1000);
        console.log(result);
        if (count > 5) {
            return '中断';
        }
        console.log(await this.setDelaySecond(2));  // 此句不执行
        console.log(await this.setDelay(1000)); // 此句不执行
        return '执行完毕';  // 此句不执行
    }

实质就是直接return返回了一个Promise,相当于return Promise.resolve('我退出了下面不进行了').

执行效果:
在这里插入图片描述

最后牢记:async函数实质就是返回一个Promise


实战中异步需要注意的地方

Promise获取数据(串行)之then写法注意

我们需要实现一个依次分别延迟1秒输出值,一共3次的程序。单次的方法如下:

   setDelay = millisecond => {
        return new Promise((resolve, reject) => {
            if (typeof millisecond != 'number') {
                reject(new Error('参数必须是number类型'));
            }
            setTimeout(() => {
                resolve(`我延迟了${millisecond}毫秒后输出的,是第1个函数setDelay`)
            }, millisecond)
        })
    }

注意不能使用Promise.all方法,因为是并行的。

下面这种写法也是并行的:同时输出。

arr = [setDelay(1000), setDelay(1000), setDelay(1000)]
arr[0]
.then(result=>{
  console.log(result)
  return arr[1]
})
.then(result=>{
  console.log(result)
  return arr[2]
})
.then(result=>{
  console.log(result)
})

因为setDelayPromisesetDelay(1000)放入数组中的时候已经执行了,也就是说数组里面保存的每个Promise状态都是resolve完成的状态了,那么你后面链式调用直接return arr[1]其实没有去请求,只是立即返回了一个resolve的状态。所以你会发现程序是相当于并行的,没有依次顺序调用。

正确的写法:

// 每隔1秒输出
    handleClick = () => {
        let arr = [this.setDelay, this.setDelay, this.setDelay]
        arr[0](1000)
            .then(result => {
                console.log(result)
                return arr[1](1000)
            })
            .then(result => {
                console.log(result)
                return arr[2](1000)
            })
            .then(result => {
                console.log(result)
            })
    }

上述相当于把Promise预先存储在一个数组中,在你需要调用的时候,再去执行。


Promise循环获取数据(串行)之for循环

实现代码:

  // 每隔2秒输出
    handleClick = () => {
        let arr = [this.timeout(2000, 1), this.timeout(2000, 2), this.timeout(2000, 3)];

        this.syncPromise(arr).then(result => {
            console.log(result);
            console.log('完成了');
        });
    }


    syncPromise = arr => {
        const _syncLoop = function (count) {
            if (count === arr.length - 1) { // 是最后一个就直接return
                return arr[count]();
            }
            return arr[count]().then((result) => {
                console.log(result);
                return _syncLoop(count + 1) // 递归调用数组下标
            });
        }
        return _syncLoop(0);
    }

    // 闭包Promise,timeout是方法不是Promise
    timeout = (millisecond, count) => {
        return () => {
            // setDelay是Promise
            return this.setDelay(millisecond, count);
        }
    }

输出结果:
在这里插入图片描述

首先你需要闭包你的Promise程序。

    // 闭包Promise,timeout是方法不是Promise
    timeout = (millisecond, count) => {
        return () => {
            // setDelay是Promise
            return this.setDelay(millisecond, count);
        }
    }

如果不闭包会导致什么后果呢?不闭包的话,你传入的参数值后,你的Promise会马上执行,导致状态改变,如果用闭包实现的话,你的Promise会一直保存着,等到你需要调用的时候再使用。而且最大的优点是可以预先传入你需要的参数。

总结:使用Promise实现循环输出的功能太繁琐。


async/await循环获取数据(串行)之for循环

实现过程非常简洁:注意arr[i]()才是方法

    // 每隔2秒输出
    handleClick = () => {
        let arr = [this.timeout(2000, 1), this.timeout(2000, 2), this.timeout(2000, 3)];
        this.demo(arr).then(result => {
            console.log(result);
        });
    }

    demo = async (arr) => {
        for (let i = 0; i < arr.length; i += 1) {
            const result = await arr[i]();
            console.log(result);
        }
        return ('执行结束');
    }


    // 闭包Promise,timeout是方法不是Promise
    timeout = (millisecond, count) => {
        return () => {
            // setDelay是Promise
            return this.setDelay(millisecond, count);
        }
    }

输出结果:
在这里插入图片描述


鸣谢和感想

之前看阮一峰大神关于Promise的文章,理解的不是很深刻,偶尔中看到Leon大神的文章《异步Promise及Async/Await可能最完整入门攻略》,按照文章完整的撸了一遍代码,对Promiseasync/await语法糖的理解更加深刻了。

感谢阮一峰大神和Leon大神,本文所有代码采用React + Ant Design Pro实现。
在这里插入图片描述

对前端的学习,最近有一些感悟:学习的过程唯有阅读、理解、实践、反馈、记录,才能真正掌握一个知识点;前端的学习就是一个个✨一样的知识点汇聚成的银河,在探索星空的过程中,唯有脚踏实力、砥砺前行,才能获得一点点的进步。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值