重學JS:async/await

william_li發表於2019-08-17

前言

非同步操作一直是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)
複製程式碼

重學JS:async/await
由例子可以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 ()
複製程式碼

上面程式碼繼發執行,所花時間是:

重學JS:async/await
實際上,這段程式碼執行順序,我並不關心,繼發執行就浪費大量執行時間,下面改成併發執行:

function call () {
  Promise.all([test(), test1(), test2()])
}
call()
複製程式碼

所花時間:

重學JS:async/await
因此在使用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()
複製程式碼

重學JS: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 = []
const promises = users.map(user => getUserInfo(user.id))
Promise.all(promises).then(res => {
  userInfos = res
  console.log(userInfos)
})
複製程式碼

重學JS:async/await
由上面例子可以看到併發執行的效率要高得多

總結

此篇文章async/await的用法和經常遇到的一些問題做了簡單的總結,希望能對大家在使用的時候有所幫助。
如果有錯誤或不嚴謹的地方,歡迎批評指正,如果喜歡,歡迎點贊

相關文章