重學JS:async/await

前言

異步操作一直是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的用法和經常遇到的一些問題做了簡單的總結,希望能對大家在使用的時候有所幫助。

如果有錯誤或不嚴謹的地方,歡迎批評指正,如果喜歡,歡迎點贊

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