终极异步处理方案async、await,相比传统异步处理方案有什么优势?

写在前面

声明

首先,这篇博客较为主观,仅代表个人观点,如果您有不同意见或者发现本文有错误,您可以留言或者加我的微信15011177648

需要的基础技能

async、await是es2017(es8)的新功能,也是Promise的语法糖,想学习async、await的话需要先了解Promise的使用
传送门:Promise使用
想了解Promise的实现原理,可以去看看我之前的博客
传送门:Promise实现原理

async、await简单介绍

  • 被async关键字修饰的函数一定返回一个promise对象
 <script>
    (async function getPromise() {
        let pro = ajax();
        console.log(pro)
    })()
    async function ajax() {
        return 1
    }
</script>

在这里插入图片描述

  • 如果这个函数又被await修饰,则返回promise的PromiseValue
<script>
    (async function getPromise() {
        let pro = await ajax();
        console.log(pro)
    })()
    async function ajax() {
        return 1
    }
</script>

在这里插入图片描述

  • async、await是一个Promise的语法糖,以下两种方式相等
<script>
    (async function () {
        await request();
        console.log('后续处理')
    })()
    async function request() {
        return new Promise((resolve, reject) => {
            ajax('', () => {
                resolve()
                console.log('请求完成')
            })
        })
    }
    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>
<script>
    (function () {
        new Promise((resolve, reject) => {
            ajax('', () => {
                resolve()
                console.log('请求完成')
            })
        }).then(() => {
            console.log('后续处理');
        })
    })()
    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

通过async与await,异步的处理就变得更优雅,更容易阅读

async、await与之前的异步处理方式的不同

我认为,async的异步处理方式与之前的异步处理方式最大的不同在于回调函数的位置
在之前的异步处理方案中,回调函数作为一个参数传入异步处理函数
而在使用async时,回调函数直接像同步函数一样写在之前函数的下方即可
之后把之前的调用方式成为传统调用,async、await叫做async调用

例:传统调用

<body>
    <div>
        <span>两个接口依次调用:</span>
        <button id="twoAPI">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#twoAPI').click(() => {
        test1()
    })

    function test1() {
        ajax('no1', () => {
            test2()
        })
    }
    function test2() {
        ajax('no2')
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

接口调用时间:
传统方法依次调用两个接口时间图
async调用

<body>
    <div>
        <span>两个接口依次调用:</span>
        <button id="twoAPI">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#twoAPI').click(async function() {
        await test1();
        test2()
    })

    function test1() {
        return new Promise((resolve, reject) => {
            ajax('no1', () => resolve())
        }) 
    }
    function test2() {
        return new Promise((resolve, reject) => {
            ajax('no2', () => resolve())
        }) 
    }

    function ajax(remark, cb = () => {}) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

接口调用时间如图
async方法依次调用两个接口时间图
可以看出两种方式在结果上都是一样的,都实现了依次调用两个接口,那么传统方法存在什么诟病呢?async关键字又是怎么解决的呢?

传统异步请求的诟病有哪些?async如何解决的?

逻辑分散

举个例子
有一个逻辑需要依次调用三个接口(上班这个动作需要依次起床,穿衣服,出门)

传统写法

<body>
    <div>
        <span>上班</span>
        <button id="GoToWork">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#GoToWork').click(() => {
        getUp()
    })

    function getUp() {
        ajax('getUp', () => {
            getDressed()
        })
    }
    function getDressed() {
        ajax('getDressed', () => {
            goOut()
        })
    }
    function goOut() {
        ajax('getDressed', () => {
            console.log('上班去了')
        })
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

当我们之后想阅读这个逻辑时,会先看button的点击事件函数,看到上班需要先起床
但是起床了就完了吗?不一定,我们需要找到起床函数,看看起床函数有没有做其他事,然后会发现起床函数还调用了穿衣服函数,
但是穿上衣服就完了吗?不一定,我们需要找到穿衣服函数,看看穿衣服函数有没有做其他事,然后会发现穿衣服函数还调用了出门函数,
但是出了门就完了吗?不一定,我们需要找到出门函数,看看出门函数有没有做其他事,最后发现出门函数调用完成以后,打印了一句“上班去了“,现在才是真的完了

这样的话一个按钮点击以后做了三件事,这三件事的逻辑被分散开了,阅读的话需要一个一个函数去找,比较麻烦,现在的例子里,代码还比较少,在展示案例中,代码行数可能有几十上百行,函数和函数也不一定就这么挨着,所以真实案例中,阅读起来比这个更困难。

结合POS系统:在我们组正在开发的POS收银系统中,就有不少这种例子,比如通过批销单开批销退货单,这时候已经选中一个批销单了,我想更换批销单,就需要依次调用清空临时表,关联新的批销单,获取临时表信息三个步骤

async写法

接下来使用async改造这个逻辑

<body>
    <div>
        <span>上班</span>
        <button id="GoToWork">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#GoToWork').click(async () => {
        await getUp()
        await getDressed()
        await goOut()
        console.log('上班去了')
    })

    function getUp() {
        return new Promise((resolve) => {
            ajax('getUp', () => resolve())
        })
    }
    function getDressed() {
        return new Promise((resolve) => {
            ajax('getDressed', () => resolve())
        })
    }
    function goOut() {
        return new Promise((resolve) => {
            ajax('goOut', () => resolve())
        })
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

这样改造后,我们再去看这个逻辑的时候,只需要看点击事件就可以知道点完这个按钮究竟发生了什么
在这里插入图片描述
逻辑以及前后顺序一目了然,省去了很多查找代码的时间。

不符合单一职责原则,语义化有问题

传统写法

还是上面的例子,我们仔细看getUp方法,从函数名称上来看,这个方法是起床,但是这个方法究竟做了什么呢?
先是调用起床方法,然后调用穿衣服方法,然后调用了出门方法,最后还打印了一句“上班去了”。
那么按照语义,这个方法应该叫做getUpAndGetDressedAndGoOut
因为这个方法做了这三件事而不是一件事
反过来说,如果它叫getUp的话,那么它应该只做起床一件事,就是起床,可是它做的事不止一件,就比较矛盾

async写法

async写法从根本上就解决了这个问题,方法本来就是分开的,一个方法只做一件事

不灵活

在传统方法中,调用getUp意味着在getUp完成后调用穿衣服和出门方法,如果我们只想调用起床方法呢?

传统写法

有两个解决方案

  1. 添加参数
<body>
    <div>
        <span>上班</span>
        <button id="GoToWork">click me</button>
    </div>
    <div>
        <span>只是起床</span>
        <button id="onlyGetUp">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#GoToWork').click(() => {
        getUp(true)
    })

    $('#onlyGetUp').click(() => {
        getUp(false)
    })

    function getUp(flag) {
        ajax('getUp', () => {
            flag && getDressed()
        })
    }
    function getDressed() {
        ajax('getDressed', () => {
            goOut()
        })
    }
    function goOut() {
        ajax('getDressed', () => {
            console.log('上班去了')
        })
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

可以通过添加参数来判断是否进行后续处理,但是这样的话就增加了代码复杂度

  1. 重写一个函数
<body>
    <div>
        <span>上班</span>
        <button id="GoToWork">click me</button>
    </div>
    <div>
        <span>只是起床</span>
        <button id="onlyGetUp">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#GoToWork').click(() => {
        getUp()
    })
    $('#onlyGetUp').click(() => {
        onlyGetUp()
    })

    function onlyGetUp() {
        ajax('getUp')
    }
    function getUp() {
        ajax('getUp', () => {
            getDressed()
        })
    }
    function getDressed() {
        ajax('getDressed', () => {
            goOut()
        })
    }
    function goOut() {
        ajax('getDressed', () => {
            console.log('上班去了')
        })
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

这样也可以完成这样的需求,但是新增一个方法就会增加不必要的冗余。

async写法

其实async的写法中,方法就像是组件,需要就调用,不需要就不调用,在这个写法中,只要不去调用其他方法就好了

<body>
    <div>
        <span>上班</span>
        <button id="GoToWork">click me</button>
    </div>
    <div>
        <span>只是起床</span>
        <button id="onlyGetUp">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#GoToWork').click(async () => {
        await getUp()
        await getDressed()
        await goOut()
        console.log('上班去了')
    })
    $('#onlyGetUp').click(async () => {
        await getUp()
    })

    function getUp() {
        return new Promise((resolve) => {
            ajax('getUp', () => resolve())
        })
    }
    function getDressed() {
        return new Promise((resolve) => {
            ajax('getDressed', () => resolve())
        })
    }
    function goOut() {
        return new Promise((resolve) => {
            ajax('goOut', () => resolve())
        })
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

既不增加参数,也不重写方法

当两个接口同时调用,都完成后再进行后续操作的时候

例子:睡觉前泡脚喝牛奶同时进行,都做完后上床

传统写法

传统写法在进行这种处理的时候,显得较为费劲,每调用完一个接口都要去判断其他接口是不是都已经完成了
写法如下

<body>
    <div>
        <span>睡觉</span>
        <button id="sleep">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#sleep').click(() => {
        drinkMilk()
        footBath()
    })

    let resultArr = [null, null]
    function drinkMilk() {
        ajax('drinkMilk', (res) => {
            resultArr[0] = res;
            for (let i = 0; i < resultArr.length; i++) {
                if (!resultArr[i]) {
                    return
                }
            }
            goToBed()
        })
    }
    function footBath() {
        ajax('footBath', (res) => {
            resultArr[1] = res;
            for (let i = 0; i < resultArr.length; i++) {
                if (!resultArr[i]) {
                    return
                }
            }
            goToBed()
        })
    }
    function goToBed() {
        ajax('goOut')
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

请求时间如图所示
在这里插入图片描述
但是,如果现在睡觉前还想追个电视剧的话,那么还要加一个新函数,新函数的回调也需要循环resultArr,这样的话,造成了太多冗余

async写法

async可以使用Promise的静态方法all来实现

<body>
    <div>
        <span>睡觉</span>
        <button id="sleep">click me</button>
    </div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    $('#sleep').click(() => {
        Promise.all([drinkMilk(), footBath()]).then(() => {
            goToBed()
        })
    })

    function drinkMilk() {
        return new Promise((resolve) => {
            ajax('drinkMilk', (res) => {
                resolve(res)
            })
        })
    }
    function footBath() {
        return new Promise((resolve) => {
            ajax('footBath', (res) => {
                resolve(res)
            })
        })
    }
    function goToBed() {
        ajax('goOut')
    }

    function ajax(remark, cb = () => { }) {
        $.ajax({
            url: `https://developer.duyiedu.com/edu/groupChat/getMsgList?remark=${remark}`,
            method: 'get',
            success: cb
        })
    }
</script>

在这里插入图片描述
这样不需要每次请求完成都去循环结果列表
结合POS系统:在我们组正在开发的POS收银系统中,打印小票就是这样的,在执行打印方法前,需要先请求到订单信息,以及配置,两个请求都完成以后才去进行打印

小结

综上,async的写法只要接口名称以及后续处理逻辑不改变,就不需要改变发起请求的函数,而在传统调用中,有很多与接口不相关的逻辑都会影响到这个接口
同时async可以将异步操作变为了与同步类似的操作,降低了代码阅读难度,还解决了回调地狱的问题

所以async await被称之为终极异步处理方案并不过分

OK,这就是本期博客的全部内容了,如果你喜欢我的博客内容的话,就点一个赞和关注吧,我们下个博客见

发布了10 篇原创文章 · 获赞 97 · 访问量 4945

猜你喜欢

转载自blog.csdn.net/qq_45516476/article/details/105349222