前言
異步操作一直是JS中不可或缺的一環(huán),從最開始回調(diào)函數(shù),到后面的Promise,再到ES2017引入的async函數(shù),異步操作逐漸進(jìn)化,變得越來越簡(jiǎn)單方便,接下來就仔細(xì)看看在ES2017引入了async函數(shù)后,異步操作產(chǎn)生了哪些變化。
有什么用
以往我們使用異步函數(shù),都是async/await一起用的,但是這回我準(zhǔn)備拆開看,分別介紹async和await有什么用
async作用
通常情況下使用async命令是因?yàn)楹瘮?shù)內(nèi)部有await命令,因?yàn)閍wait命令只能出現(xiàn)在async函數(shù)里面,否則會(huì)報(bào)語法,這就是為什么async/await成對(duì)出現(xiàn)的原因,但是如果對(duì)一個(gè)普通函數(shù)單獨(dú)加個(gè)async會(huì)是什么結(jié)果呢?來看個(gè)例子:
async function test () {
let a = 2
return a
}
const res = test()
console.log(res)

由例子可以async函數(shù)返回的是一個(gè)Promise對(duì)象,如果函數(shù)中有返回值,async會(huì)把這個(gè)返回值通過Promise.resole()封裝成Promise對(duì)象,要取這個(gè)值也很簡(jiǎn)單,直接通過then()就能取出,如例:
res.then(a => {
console.log(a) // 2
})
在沒有await的情況下,調(diào)用async函數(shù),會(huì)立即執(zhí)行,返回一個(gè)Promise,那加上await會(huì)有什么不同呢?
await作用
一般情況下,await命令后面接的是一個(gè)Promise對(duì)象,等待Promise對(duì)象狀態(tài)發(fā)生變化,得到返回值,但是也可以接任意表達(dá)式的返回結(jié)果,來看個(gè)例子:
function a () {
return 'a'
}
async function b () {
return 'b'
}
const c = await a()
const d = await b()
console.log(c, d) // 'a' 'b'
復(fù)制代碼由例子可以看到await后面不管接的是什么表達(dá)式,都能等待到結(jié)果的返回,當(dāng)?shù)鹊讲皇荘romise對(duì)象時(shí),就將等到的結(jié)果返回,當(dāng)?shù)鹊降氖且粋€(gè)Promise對(duì)象時(shí),會(huì)阻塞后面的代碼,等待Promise對(duì)象狀態(tài)變化,得到對(duì)應(yīng)的值作為await等待的結(jié)果,這里的阻塞指的是async內(nèi)部的阻塞,async函數(shù)的調(diào)用并不會(huì)阻塞
解決了什么問題
Promise...then語法已經(jīng)解決了以前一直存在的多層回調(diào)嵌套的問題,那問什么還要用async/await呢?要解答這個(gè)問題先來看一段Promise代碼:
function login () {
return new Promise(resolve => {
resolve('aaaa')
})
}
function getUserInfo (token) {
return new Promise(resolve => {
if (token) {
resolve({
isVip: true
})
}
})
}
function getVipGoods (userInfo) {
return new Promise(resolve => {
if (userInfo.isVip) {
resolve({
id: 'xxx',
price: 'xxx'
})
}
})
}
function showVipGoods (vipGoods) {
console.log(vipGoods.id + '----' + vipGoods.price)
}
login()
.then(token => getUserInfo(token))
.then(userInfo => getVipGoods(userInfo))
.then(vipGoods => showVipGoods(vipGoods))
如例子所示,每一個(gè)Promise相當(dāng)于一個(gè)異步的網(wǎng)絡(luò)請(qǐng)求,通常一個(gè)業(yè)務(wù)流程需要多個(gè)網(wǎng)絡(luò)請(qǐng)求,而且網(wǎng)絡(luò)請(qǐng)求網(wǎng)絡(luò)請(qǐng)求都依賴一個(gè)的請(qǐng)求結(jié)果,上例就是Promise模擬了這個(gè)過程,下面我們?cè)賮砜纯从胊sync/await會(huì)有什么不同,如例:
async function call() {
const token = await login()
const userInfo = await getUserInfo(token)
const vipGoods = await getVipGoods(userInfo)
showVipGoods(vipGoods)
}
call()
和Promise的then鏈調(diào)用相比,async/await的調(diào)用更加清晰簡(jiǎn)單,和同步代碼一樣
帶來了什么問題
使用async/await我們經(jīng)常會(huì)忽略一個(gè)問題,同步執(zhí)行帶來的時(shí)間累加,會(huì)導(dǎo)致程序變慢,有時(shí)候我們的代碼可以寫成并發(fā)執(zhí)行,但是由于async/await做成了繼發(fā)執(zhí)行,來看一個(gè)例子:
function test () {
return new Promise(resolve => {
setTimeout(() => {
console.log('test')
resolve()
}, 1000)
})
}
function test1 () {
return new Promise(resolve => {
setTimeout(() => {
console.log('test1')
resolve()
}, 1000)
})
}
function test2 () {
return new Promise(resolve => {
setTimeout(() => {
console.log('test2')
resolve()
}, 1000)
})
}
async function call () {
await test()
await test1()
await test2()
}
call ()
上面代碼繼發(fā)執(zhí)行,所花時(shí)間是:

實(shí)際上,這段代碼執(zhí)行順序,我并不關(guān)心,繼發(fā)執(zhí)行就浪費(fèi)大量執(zhí)行時(shí)間,下面改成并發(fā)執(zhí)行:
function call () {
Promise.all([test(), test1(), test2()])
}
call()
所花時(shí)間:

因此在使用async/await時(shí)需要特別注意這一點(diǎn)
循環(huán)中的小問題
在寫JS循環(huán)時(shí),JS提供了許多好用數(shù)組api接口,forEach就是其中一個(gè),但是碰上了async/await,可能就悲劇了,得到了不是你想要的結(jié)果,來看一個(gè)例子:
function getUserInfo (id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
id: id,
name: 'xxx',
age: 'xxx'
})
}, 200)
})
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
users.forEach(async user => {
let info = await getUserInfo(user.id)
userInfos.push(info)
})
console.log(userInfos) // []
上面這段代碼是不是很熟悉,模擬獲取多個(gè)用戶的用戶信息,然后得到一個(gè)用戶信息數(shù)組,但是很遺憾,上面的userInfos得到的是一個(gè)空數(shù)組,上面這段代碼加上了async/await后,forEach循環(huán)就變成了異步的,因此不會(huì)等到所有用戶信息都請(qǐng)求完才打印userInfos,想要等待結(jié)果的返回再打印,還是要回到老式的for循環(huán),來看代碼:
function getUserInfo (id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
id: id,
name: 'xxx',
age: 'xxx'
})
}, 200)
})
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
async function call() {
for (user of users) {
let info = await getUserInfo(user.id)
userInfos.push(info)
}
console.log(userInfos)
}
call()

上面這種寫法是繼發(fā)式的,也就是會(huì)等前面一個(gè)任務(wù)執(zhí)行完,再執(zhí)行下一個(gè),但是也許你并不關(guān)心執(zhí)行過程,只要拿到想要的結(jié)果就行了,這時(shí)并發(fā)式的效率會(huì)更高,來看代碼:
function getUserInfo (id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
id: id,
name: 'xxx',
age: 'xxx'
})
}, 200)
})
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
const promises = users.map(user => getUserInfo(user.id))
Promise.all(promises).then(res => {
userInfos = res
console.log(userInfos)
})

由上面例子可以看到并發(fā)執(zhí)行的效率要高得多
總結(jié)
此篇文章async/await的用法和經(jīng)常遇到的一些問題做了簡(jiǎn)單的總結(jié),希望能對(duì)大家在使用的時(shí)候有所幫助。如果有錯(cuò)誤或不嚴(yán)謹(jǐn)?shù)牡胤剑瑲g迎批評(píng)指正,如果喜歡,歡迎點(diǎn)贊