7張圖,20分鐘就能搞定的async/await原理!
前言
之前我發(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()

其實(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?}

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?}

其實(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?}
??})
})

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?}

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?}
????})
??})
})

實(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ì)嵌套幾次

那我們?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í)群
