終極異步處理方案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,這就是本期博客的全部內容了,如果你喜歡我的博客內容的話,就點一個贊和關注吧,我們下個博客見

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章