前言
非同步操作一直是JS中不可或缺的一環,從最開始回撥函式,到後面的Promise,再到ES2017引入的async函式,非同步操作逐漸進化,變得越來越簡單方便,接下來就仔細看看在ES2017引入了async函式後,非同步操作產生了哪些變化。
有什麼用
以往我們使用非同步函式,都是async/await一起用的,但是這回我準備拆開看,分別介紹async和await有什麼用
async作用
通常情況下使用async命令是因為函式內部有await命令,因為await命令只能出現在async函式裡面,否則會報語法,這就是為什麼async/await成對出現的原因,但是如果對一個普通函式單獨加個async會是什麼結果呢?來看個例子:
async function test () {
let a = 2
return a
}
const res = test()
console.log(res)
複製程式碼
由例子可以async函式返回的是一個Promise物件,如果函式中有返回值,async會把這個返回值通過Promise.resole()封裝成Promise物件,要取這個值也很簡單,直接通過then()就能取出,如例:
res.then(a => {
console.log(a) // 2
})
複製程式碼
在沒有await的情況下,呼叫async函式,會立即執行,返回一個Promise,那加上await會有什麼不同呢?
await作用
一般情況下,await命令後面接的是一個Promise物件,等待Promise物件狀態發生變化,得到返回值,但是也可以接任意表示式的返回結果,來看個例子:
function a () {
return 'a'
}
async function b () {
return 'b'
}
const c = await a()
const d = await b()
console.log(c, d) // 'a' 'b'
複製程式碼
由例子可以看到await後面不管接的是什麼表示式,都能等待到結果的返回,當等到不是Promise物件時,就將等到的結果返回,當等到的是一個Promise物件時,會阻塞後面的程式碼,等待Promise物件狀態變化,得到對應的值作為await等待的結果,這裡的阻塞指的是async內部的阻塞,async函式的呼叫並不會阻塞
解決了什麼問題
Promise...then語法已經解決了以前一直存在的多層回撥巢狀的問題,那問什麼還要用async/await呢?要解答這個問題先來看一段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))
複製程式碼
如例子所示,每一個Promise相當於一個非同步的網路請求,通常一個業務流程需要多個網路請求,而且網路請求網路請求都依賴一個的請求結果,上例就是Promise模擬了這個過程,下面我們再來看看用async/await會有什麼不同,如例:
async function call() {
const token = await login()
const userInfo = await getUserInfo(token)
const vipGoods = await getVipGoods(userInfo)
showVipGoods(vipGoods)
}
call()
複製程式碼
和Promise的then鏈呼叫相比,async/await的呼叫更加清晰簡單,和同步程式碼一樣
帶來了什麼問題
使用async/await我們經常會忽略一個問題,同步執行帶來的時間累加,會導致程式變慢,有時候我們的程式碼可以寫成併發執行,但是由於async/await做成了繼發執行,來看一個例子:
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 ()
複製程式碼
上面程式碼繼發執行,所花時間是:
實際上,這段程式碼執行順序,我並不關心,繼發執行就浪費大量執行時間,下面改成併發執行:function call () {
Promise.all([test(), test1(), test2()])
}
call()
複製程式碼
所花時間:
因此在使用async/await時需要特別注意這一點迴圈中的小問題
在寫JS迴圈時,JS提供了許多好用陣列api介面,forEach就是其中一個,但是碰上了async/await,可能就悲劇了,得到了不是你想要的結果,來看一個例子:
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) // []
複製程式碼
上面這段程式碼是不是很熟悉,模擬獲取多個使用者的使用者資訊,然後得到一個使用者資訊陣列,但是很遺憾,上面的userInfos得到的是一個空陣列,上面這段程式碼加上了async/await後,forEach迴圈就變成了非同步的,因此不會等到所有使用者資訊都請求完才列印userInfos,想要等待結果的返回再列印,還是要回到老式的for迴圈,來看程式碼:
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()
複製程式碼
上面這種寫法是繼髮式的,也就是會等前面一個任務執行完,再執行下一個,但是也許你並不關心執行過程,只要拿到想要的結果就行了,這時併發式的效率會更高,來看程式碼:
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)
})
複製程式碼
由上面例子可以看到併發執行的效率要高得多
總結
此篇文章async/await的用法和經常遇到的一些問題做了簡單的總結,希望能對大家在使用的時候有所幫助。
如果有錯誤或不嚴謹的地方,歡迎批評指正,如果喜歡,歡迎點贊