1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        7張圖,20分鐘就能搞定的async/await原理!

        共 9728字,需瀏覽 20分鐘

         ·

        2021-12-19 15:16

        前言

        之前我發(fā)過(guò)一篇手寫(xiě)Promise原理,最通俗易懂的版本?。?!,帶大家基本了解了Promise內(nèi)部的實(shí)現(xiàn)原理,而提到Promise,就不得不提一個(gè)東西,那就是async/await,async/await是一個(gè)很重要的語(yǔ)法糖,他的作用是用同步的方式,執(zhí)行異步操作。那么今天我就帶大家一起實(shí)現(xiàn)一下async/await吧?。?!

        async/await用法

        其實(shí)你要實(shí)現(xiàn)一個(gè)東西之前,最好是先搞清楚這兩樣?xùn)|西

        • 這個(gè)東西有什么用?
        • 這個(gè)東西是怎么用的?

        有什么用?

        async/await的用處就是:用同步方式,執(zhí)行異步操作,怎么說(shuō)呢?舉個(gè)例子

        比如我現(xiàn)在有一個(gè)需求:先請(qǐng)求完接口1,再去請(qǐng)求接口2,我們通常會(huì)這么做

        function?request(num)?{?//?模擬接口請(qǐng)求
        ??return?new?Promise(resolve?=>?{
        ????setTimeout(()?=>?{
        ??????resolve(num?*?2)
        ????},?1000)
        ??})
        }

        request(1).then(res1?=>?{
        ??console.log(res1)?//?1秒后?輸出?2

        ??request(2).then(res2?=>?{
        ????console.log(res2)?//?2秒后?輸出?4
        ??})
        })

        或者我現(xiàn)在又有一個(gè)需求:先請(qǐng)求完接口1,再拿接口1返回的數(shù)據(jù),去當(dāng)做接口2的請(qǐng)求參數(shù),那我們也可以這么做

        request(5).then(res1?=>?{
        ??console.log(res1)?//?1秒后?輸出?10

        ??request(res1).then(res2?=>?{
        ????console.log(res2)?//?2秒后?輸出?20
        ??})
        })

        其實(shí)這么做是沒(méi)問(wèn)題的,但是如果嵌套的多了,不免有點(diǎn)不雅觀,這個(gè)時(shí)候就可以用async/await來(lái)解決了

        async?function?fn?()?{
        ??const?res1?=?await?request(5)
        ??const?res2?=?await?request(res1)
        ??console.log(res2)?//?2秒后輸出?20
        }
        fn()

        是怎么用?

        還是用剛剛的例子

        需求一:

        async?function?fn?()?{
        ??await?request(1)
        ??await?request(2)
        ??//?2秒后執(zhí)行完
        }
        fn()

        需求二:

        async?function?fn?()?{
        ??const?res1?=?await?request(5)
        ??const?res2?=?await?request(res1)
        ??console.log(res2)?//?2秒后輸出?20
        }
        fn()
        截屏2021-09-11 下午9.57.58.png

        其實(shí)就類(lèi)似于生活中的排隊(duì),咱們生活中排隊(duì)買(mǎi)東西,肯定是要上一個(gè)人買(mǎi)完,才輪到下一個(gè)人。而上面也一樣,在async函數(shù)中,await規(guī)定了異步操作只能一個(gè)一個(gè)排隊(duì)執(zhí)行,從而達(dá)到用同步方式,執(zhí)行異步操作的效果,這里注意了:await只能在async函數(shù)中使用,不然會(huì)報(bào)錯(cuò)哦

        剛剛上面的例子await后面都是跟著異步操作Promise,那如果不接Promise會(huì)怎么樣呢?

        function?request(num)?{?//?去掉Promise
        ??setTimeout(()?=>?{
        ????console.log(num?*?2)
        ??},?1000)
        }

        async?function?fn()?{
        ??await?request(1)?//?2
        ??await?request(2)?//?4
        ??//?1秒后執(zhí)行完??同時(shí)輸出
        }
        fn()

        可以看出,如果await后面接的不是Promise的話,有可能其實(shí)是達(dá)不到排隊(duì)的效果的

        說(shuō)完await,咱們聊聊async吧,async是一個(gè)位于function之前的前綴,只有async函數(shù)中,才能使用await。那async執(zhí)行完是返回一個(gè)什么東西呢?

        async?function?fn?()?{}
        console.log(fn)?//?[AsyncFunction:?fn]
        console.log(fn())?//?Promise?{:?undefined}

        可以看出,async函數(shù)執(zhí)行完會(huì)自動(dòng)返回一個(gè)狀態(tài)為fulfilled的Promise,也就是成功狀態(tài),但是值卻是undefined,那要怎么才能使值不是undefined呢?很簡(jiǎn)單,函數(shù)有return返回值就行了

        async?function?fn?(num)?{
        ??return?num
        }
        console.log(fn)?//?[AsyncFunction:?fn]
        console.log(fn(10))?//?Promise?{:?10}
        fn(10).then(res?=>?console.log(res))?//?10

        可以看出,此時(shí)就有值了,并且還能使用then方法進(jìn)行輸出

        總結(jié)

        總結(jié)一下async/await的知識(shí)點(diǎn)

        • await只能在async函數(shù)中使用,不然會(huì)報(bào)錯(cuò)
        • async函數(shù)返回的是一個(gè)Promise對(duì)象,有無(wú)值看有無(wú)return值
        • await后面最好是接Promise,雖然接其他值也能達(dá)到排隊(duì)效果
        • async/await作用是用同步方式,執(zhí)行異步操作

        什么是語(yǔ)法糖?

        前面說(shuō)了,async/await是一種語(yǔ)法糖,誒!好多同學(xué)就會(huì)問(wèn),啥是語(yǔ)法糖呢?我個(gè)人理解就是,語(yǔ)法糖就是一個(gè)東西,這個(gè)東西你就算不用他,你用其他手段也能達(dá)到這個(gè)東西同樣的效果,但是可能就沒(méi)有這個(gè)東西這么方便了。

        • 舉個(gè)生活中的例子吧:你走路也能走到北京,但是你坐飛機(jī)會(huì)更快到北京。
        • 舉個(gè)代碼中的例子吧:ES6的class也是語(yǔ)法糖,因?yàn)槠鋵?shí)用普通function也能實(shí)現(xiàn)同樣效果

        回歸正題,async/await是一種語(yǔ)法糖,那就說(shuō)明用其他方式其實(shí)也可以實(shí)現(xiàn)他的效果,我們今天就是講一講怎么去實(shí)現(xiàn)async/await,用到的是ES6里的迭代函數(shù)——generator函數(shù)

        generator函數(shù)

        基本用法

        generator函數(shù)跟普通函數(shù)在寫(xiě)法上的區(qū)別就是,多了一個(gè)星號(hào)*,并且只有在generator函數(shù)中才能使用yield,什么是yield呢,他相當(dāng)于generator函數(shù)執(zhí)行的中途暫停點(diǎn),比如下方有3個(gè)暫停點(diǎn)。而怎么才能暫停后繼續(xù)走呢?那就得使用到next方法next方法執(zhí)行后會(huì)返回一個(gè)對(duì)象,對(duì)象中有value 和 done兩個(gè)屬性

        • value:暫停點(diǎn)后面接的值,也就是yield后面接的值
        • done:是否generator函數(shù)已走完,沒(méi)走完為false,走完為true
        function*?gen()?{
        ??yield?1
        ??yield?2
        ??yield?3
        }
        const?g?=?gen()
        console.log(g.next())?//?{?value:?1,?done:?false?}
        console.log(g.next())?//?{?value:?2,?done:?false?}
        console.log(g.next())?//?{?value:?3,?done:?false?}
        console.log(g.next())?//?{?value:?undefined,?done:?true?}

        可以看到最后一個(gè)是undefined,這取決于你generator函數(shù)有無(wú)返回值

        function*?gen()?{
        ??yield?1
        ??yield?2
        ??yield?3
        ??return?4
        }
        const?g?=?gen()
        console.log(g.next())?//?{?value:?1,?done:?false?}
        console.log(g.next())?//?{?value:?2,?done:?false?}
        console.log(g.next())?//?{?value:?3,?done:?false?}
        console.log(g.next())?//?{?value:?4,?done:?true?}
        截屏2021-09-11 下午9.46.17.png

        yield后面接函數(shù)

        yield后面接函數(shù)的話,到了對(duì)應(yīng)暫停點(diǎn)yield,會(huì)馬上執(zhí)行此函數(shù),并且該函數(shù)的執(zhí)行返回值,會(huì)被當(dāng)做此暫停點(diǎn)對(duì)象的value

        function?fn(num)?{
        ??console.log(num)
        ??return?num
        }
        function*?gen()?{
        ??yield?fn(1)
        ??yield?fn(2)
        ??return?3
        }
        const?g?=?gen()
        console.log(g.next())?
        //?1
        //?{?value:?1,?done:?false?}
        console.log(g.next())
        //?2
        //??{?value:?2,?done:?false?}
        console.log(g.next())?
        //?{?value:?3,?done:?true?}

        yield后面接Promise

        前面說(shuō)了,函數(shù)執(zhí)行返回值會(huì)當(dāng)做暫停點(diǎn)對(duì)象的value值,那么下面例子就可以理解了,前兩個(gè)的value都是pending狀態(tài)的Promise對(duì)象

        function?fn(num)?{
        ??return?new?Promise(resolve?=>?{
        ????setTimeout(()?=>?{
        ??????resolve(num)
        ????},?1000)
        ??})
        }
        function*?gen()?{
        ??yield?fn(1)
        ??yield?fn(2)
        ??return?3
        }
        const?g?=?gen()
        console.log(g.next())?//?{?value:?Promise?{??},?done:?false?}
        console.log(g.next())?//?{?value:?Promise?{??},?done:?false?}
        console.log(g.next())?//?{?value:?3,?done:?true?}
        截屏2021-09-11 下午10.51.38.png

        其實(shí)我們想要的結(jié)果是,兩個(gè)Promise的結(jié)果1 和 2,那怎么做呢?很簡(jiǎn)單,使用Promise的then方法就行了

        const?g?=?gen()
        const?next1?=?g.next()
        next1.value.then(res1?=>?{
        ??console.log(next1)?//?1秒后輸出?{?value:?Promise?{?1?},?done:?false?}
        ??console.log(res1)?//?1秒后輸出?1

        ??const?next2?=?g.next()
        ??next2.value.then(res2?=>?{
        ????console.log(next2)?//?2秒后輸出?{?value:?Promise?{?2?},?done:?false?}
        ????console.log(res2)?//?2秒后輸出?2
        ????console.log(g.next())?//?2秒后輸出?{?value:?3,?done:?true?}
        ??})
        })
        截屏2021-09-11 下午10.38.37.png

        next函數(shù)傳參

        generator函數(shù)可以用next方法來(lái)傳參,并且可以通過(guò)yield來(lái)接收這個(gè)參數(shù),注意兩點(diǎn)

        • 第一次next傳參是沒(méi)用的,只有從第二次開(kāi)始next傳參才有用
        • next傳值時(shí),要記住順序是,先右邊yield,后左邊接收參數(shù)
        function*?gen()?{
        ??const?num1?=?yield?1
        ??console.log(num1)
        ??const?num2?=?yield?2
        ??console.log(num2)
        ??return?3
        }
        const?g?=?gen()
        console.log(g.next())?//?{?value:?1,?done:?false?}
        console.log(g.next(11111))
        //?11111
        //??{?value:?2,?done:?false?}
        console.log(g.next(22222))?
        //?22222
        //?{?value:?3,?done:?true?}
        截屏2021-09-11 下午10.53.02.png

        Promise+next傳參

        前面講了

        • yield后面接Promise
        • next函數(shù)傳參

        那這兩個(gè)組合起來(lái)會(huì)是什么樣呢?

        function?fn(nums)?{
        ??return?new?Promise(resolve?=>?{
        ????setTimeout(()?=>?{
        ??????resolve(nums?*?2)
        ????},?1000)
        ??})
        }
        function*?gen()?{
        ??const?num1?=?yield?fn(1)
        ??const?num2?=?yield?fn(num1)
        ??const?num3?=?yield?fn(num2)
        ??return?num3
        }
        const?g?=?gen()
        const?next1?=?g.next()
        next1.value.then(res1?=>?{
        ??console.log(next1)?//?1秒后同時(shí)輸出?{?value:?Promise?{?2?},?done:?false?}
        ??console.log(res1)?//?1秒后同時(shí)輸出?2

        ??const?next2?=?g.next(res1)?//?傳入上次的res1
        ??next2.value.then(res2?=>?{
        ????console.log(next2)?//?2秒后同時(shí)輸出?{?value:?Promise?{?4?},?done:?false?}
        ????console.log(res2)?//?2秒后同時(shí)輸出?4

        ????const?next3?=?g.next(res2)?//?傳入上次的res2
        ????next3.value.then(res3?=>?{
        ??????console.log(next3)?//?3秒后同時(shí)輸出?{?value:?Promise?{?8?},?done:?false?}
        ??????console.log(res3)?//?3秒后同時(shí)輸出?8

        ???????//?傳入上次的res3
        ??????console.log(g.next(res3))?//?3秒后同時(shí)輸出?{?value:?8,?done:?true?}
        ????})
        ??})
        })
        截屏2021-09-11 下午11.05.44.png

        實(shí)現(xiàn)async/await

        其實(shí)上方的generator函數(shù)Promise+next傳參,就很像async/await了,區(qū)別在于

        • gen函數(shù)執(zhí)行返回值不是Promise,asyncFn執(zhí)行返回值是Promise
        • gen函數(shù)需要執(zhí)行相應(yīng)的操作,才能等同于asyncFn的排隊(duì)效果
        • gen函數(shù)執(zhí)行的操作是不完善的,因?yàn)椴⒉淮_定有幾個(gè)yield,不確定會(huì)嵌套幾次
        截屏2021-09-11 下午11.53.41.png

        那我們?cè)趺崔k呢?我們可以封裝一個(gè)高階函數(shù)。什么是高階函數(shù)呢?高階函數(shù)的特點(diǎn)是:參數(shù)是函數(shù),返回值也可以是函數(shù)。下方的highorderFn就是一個(gè)高階函數(shù)

        function?highorderFn(函數(shù))?{
        ????//?一系列處理
        ????
        ????return?函數(shù)
        }

        我們可以封裝一個(gè)高階函數(shù),接收一個(gè)generator函數(shù),并經(jīng)過(guò)一系列處理,返回一個(gè)具有async函數(shù)功能的函數(shù)

        function?generatorToAsync(generatorFn)?{
        ??//?經(jīng)過(guò)一系列處理
        ??
        ??return?具有async函數(shù)功能的函數(shù)
        }

        返回值Promise

        之前我們說(shuō)到,async函數(shù)的執(zhí)行返回值是一個(gè)Promise,那我們要怎么實(shí)現(xiàn)相同的結(jié)果呢

        function*?gen()?{

        }

        const?asyncFn?=?generatorToAsync(gen)

        console.log(asyncFn())?//?期望這里輸出?Promise

        其實(shí)很簡(jiǎn)單,generatorToAsync函數(shù)里做一下處理就行了

        function*?gen()?{

        }
        function?generatorToAsync?(generatorFn)?{
        ??return?function?()?{
        ????return?new?Promise((resolve,?reject)?=>?{

        ????})
        ??}
        }

        const?asyncFn?=?generatorToAsync(gen)

        console.log(asyncFn())?//?Promise

        加入一系列操作

        咱們把之前的處理代碼,加入generatorToAsync函數(shù)

        function?fn(nums)?{
        ??return?new?Promise(resolve?=>?{
        ????setTimeout(()?=>?{
        ??????resolve(nums?*?2)
        ????},?1000)
        ??})
        }
        function*?gen()?{
        ??const?num1?=?yield?fn(1)
        ??const?num2?=?yield?fn(num1)
        ??const?num3?=?yield?fn(num2)
        ??return?num3
        }
        function?generatorToAsync(generatorFn)?{
        ??return?function?()?{
        ????return?new?Promise((resolve,?reject)?=>?{
        ??????const?g?=?generatorFn()
        ??????const?next1?=?g.next()
        ??????next1.value.then(res1?=>?{

        ????????const?next2?=?g.next(res1)?//?傳入上次的res1
        ????????next2.value.then(res2?=>?{

        ??????????const?next3?=?g.next(res2)?//?傳入上次的res2
        ??????????next3.value.then(res3?=>?{

        ????????????//?傳入上次的res3
        ????????????resolve(g.next(res3).value)
        ??????????})
        ????????})
        ??????})
        ????})
        ??}
        }

        const?asyncFn?=?generatorToAsync(gen)

        asyncFn().then(res?=>?console.log(res))?//?3秒后輸出?8

        可以發(fā)現(xiàn),咱們其實(shí)已經(jīng)實(shí)現(xiàn)了以下的async/await的結(jié)果了

        async?function?asyncFn()?{
        ??const?num1?=?await?fn(1)
        ??const?num2?=?await?fn(num1)
        ??const?num3?=?await?fn(num2)
        ??return?num3
        }
        asyncFn().then(res?=>?console.log(res))?//?3秒后輸出?8

        完善代碼

        上面的代碼其實(shí)都是死代碼,因?yàn)橐粋€(gè)async函數(shù)中可能有2個(gè)await,3個(gè)await,5個(gè)await ,其實(shí)await的個(gè)數(shù)是不確定的。同樣類(lèi)比,generator函數(shù)中,也可能有2個(gè)yield,3個(gè)yield,5個(gè)yield,所以咱們得把代碼寫(xiě)成活的才行

        function?generatorToAsync(generatorFn)?{
        ??return?function()?{
        ????const?gen?=?generatorFn.apply(this,?arguments)?//?gen有可能傳參

        ????//?返回一個(gè)Promise
        ????return?new?Promise((resolve,?reject)?=>?{

        ??????function?go(key,?arg)?{
        ????????let?res
        ????????try?{
        ??????????res?=?gen[key](arg)?//?這里有可能會(huì)執(zhí)行返回reject狀態(tài)的Promise
        ????????}?catch?(error)?{
        ??????????return?reject(error)?//?報(bào)錯(cuò)的話會(huì)走catch,直接reject
        ????????}

        ????????//?解構(gòu)獲得value和done
        ????????const?{?value,?done?}?=?res
        ????????if?(done)?{
        ??????????//?如果done為true,說(shuō)明走完了,進(jìn)行resolve(value)
        ??????????return?resolve(value)
        ????????}?else?{
        ??????????//?如果done為false,說(shuō)明沒(méi)走完,還得繼續(xù)走

        ??????????// value有可能是:常量,Promise,Promise有可能是成功或者失敗
        ??????????return?Promise.resolve(value).then(val?=>?go('next',?val),?err?=>?go('throw',?err))
        ????????}
        ??????}

        ??????go("next")?//?第一次執(zhí)行
        ????})
        ??}
        }

        const?asyncFn?=?generatorToAsync(gen)

        asyncFn().then(res?=>?console.log(res))

        這樣的話,無(wú)論是多少個(gè)yield都會(huì)排隊(duì)執(zhí)行了,咱們把代碼寫(xiě)成活的了

        示例

        async/await版本

        async?function?asyncFn()?{
        ??const?num1?=?await?fn(1)
        ??console.log(num1)?//?2
        ??const?num2?=?await?fn(num1)
        ??console.log(num2)?//?4
        ??const?num3?=?await?fn(num2)
        ??console.log(num3)?//?8
        ??return?num3
        }
        const?asyncRes?=?asyncFn()
        console.log(asyncRes)?//?Promise
        asyncRes.then(res?=>?console.log(res))?//?8

        使用generatorToAsync函數(shù)的版本

        function*?gen()?{
        ??const?num1?=?yield?fn(1)
        ??console.log(num1)?//?2
        ??const?num2?=?yield?fn(num1)
        ??console.log(num2)?//?4
        ??const?num3?=?yield?fn(num2)
        ??console.log(num3)?//?8
        ??return?num3
        }

        const?genToAsync?=?generatorToAsync(gen)
        const?asyncRes?=?genToAsync()
        console.log(asyncRes)?//?Promise
        asyncRes.then(res?=>?console.log(res))?//?8

        結(jié)語(yǔ)

        如果你覺(jué)得此文對(duì)你有一丁點(diǎn)幫助,點(diǎn)個(gè)贊,鼓勵(lì)一下哈哈。

        如果你想一起學(xué)習(xí)前端或者摸魚(yú),那你可以加我,加入我的摸魚(yú)學(xué)習(xí)群



        瀏覽 49
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            色精品视频 | 99视频免费在线观看 | 亚洲午夜精品成人毛片 | 黄色超碰 | 污污的视频网站 | 男女成人 免费视频在线观看 | 久久免费福利 | 靠逼网站视频在线观看 | 超碰自拍 | 国产在线秘 麻豆精品观看照片 |