異步編程之 async/await 函數

提起異步編程,你們可能會想到事件監聽、回調函數、發佈訂閱、Promise 對象、Generator 函數、async 函數等,本篇主要講解的是 async 函數,不少人認爲它是異步編程的終極解決方案。面試

1、async 函數是什麼?

摘自阮老師的文章:一句話,它就是 Generator 函數的語法糖;也有人說它是 Promise 的語法糖。編程

若是你對 Promise 對象、 Generator 函數不是特別瞭解的話,建議先看一下阮老師 ECMAScript6 入門中的關於 Promise 對象 和 Generator 函數的介紹。promise

2、async

1.async 聲明的函數的返回本質上是一個 promise 對象(很重要。。。)瀏覽器

就是說只要你聲明瞭這個函數是 async,那麼內部無論你怎麼處理,它的返回確定是個 Promise。bash

async function myAsync () {
   return 'hello world'
}
let result = myAsync()
console.log(result)
複製代碼

2.async 函數內部 return 語句返回的值,會成爲 then 方法回調函數的參數異步

myAsync().then((val) => {
   console.log(val)
})
複製代碼

3.async 函數返回的 Promise 對象,必須等到內部全部 await 命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到 return 語句或者拋出錯誤。async

也就是說,只有 async 函數內部的異步操做執行完,纔會執行 then 方法指定的回調函數。異步編程

function getNum () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1000)
    }, 1000)
  })
}

async function myAsync () {
  let num = await getNum()
  return num + 1000
}

myAsync().then((val) => {
  console.log(val)
})
複製代碼

上面代碼中,函數 myAsync 內部有兩個操做:獲取 num,加 1000 後並返回結果。只有這兩個操做所有完成,纔會執行 then 方法裏面的 console.log(val)。函數

3、await

1.正常狀況下,await 命令後面是一個 Promise 對象,返回該對象的結果。若是不是 Promise 對象,就直接返回對應的值。ui

2.await 的意思就是讓 JavaScript 引擎等待,直到 await 命令執行完畢,而後繼續執行 await 命令後面的代碼。

3.這個行爲不會耗費 CPU 資源,由於引擎能夠同時處理其餘任務:執行其餘腳本,處理事件等。

咱們來看個例子,能夠試着寫出執行結果。

function myAwait () {
   return new Promise((resolve) => {
	 resolve('hello world!')
   })
}

async function myAsync(){
   console.log('async begin')
   let wait = await myAwait()
   console.log(wait)
   console.log('async end')
   return wait
}

console.log('begin')
let result = myAsync()
console.log(result)
console.log('end')
複製代碼

以上結果是在谷歌瀏覽器下執行的結果,「async end」 在 「hello world!」 以後輸出,由於 await 阻塞了 「async end」 的輸出,可是 ‘end’ 在 「hello world!」以前輸出了,由於 await 只會阻塞 async 函數中 await 後面代碼的執行,不會阻塞其餘代碼的執行。

4、Promise 、Generator、async 異步編程示例

瞭解了 async 和 await 後,咱們一塊兒來看一個完整的例子。

假如咱們作完一件事,須要分三個步驟,每個步驟都須要上一步的執行結果,咱們分別看一下 Promise 、 Generator 和 async 都是怎麼實現的。

/* 花費時間 */
function takeLongTime (n) {
  return new Promise(resolve => {
    setTimeout(() => resolve(n + 1000), n)
  })
}

/* 步驟一 */
function step1 (n) {
  console.log(`step1 with ${n}`)
  return takeLongTime(n)
}

/* 步驟二 */
function step2 (n) {
  console.log(`step2 with ${n}`)
  return takeLongTime(n)
}

/* 步驟三 */
function step3 (n) {
  console.log(`step3 with ${n}`)
  return takeLongTime(n)
}

複製代碼

1. Promise 的實現:

function doIt () {
  let time1 = 1000
  step1(time1)
      .then(time2 => step2(time2))
      .then(time3 => step3(time3))
      .then(result => {
        console.log(`result is ${result}`)
      })
}

doIt()
複製代碼

2. Generator 的實現:

/** 執行器
  * Generator 函數不能自動執行,咱們須要藉助執行器
*/
function run (generator) {
  let iterator = generator()
  let result = iterator.next()
  function step () {
    if(!result.done) {
      let promise = Promise.resolve(result.value)
      promise.then((value) => {
        result = iterator.next(value)
        step()
      }).catch((error) => {
        result = iterator.throw(error)
        step()
      })
    }
  }
  step()
}

function *doIt () {
  let time1 = 1000
  let time2 = yield step1(time1)
  let time3 = yield step2(time2)
  let result = yield step3(time3)
  console.log(`result is ${result}`)
}

run(doIt)
複製代碼

3. async 的實現:

async function doIt () {
  let time1 = 1000
  let time2 = await step1(time1)
  let time3 = await step2(time2)
  let result = await step3(time3)
  console.log(`result is ${result}`)
}

doIt()
複製代碼

三種方法執行結果都以下:

對比以上三種實現方式:

1.因爲 Promise 的 then 方法返回的是一個新的 Promise,因此 Promise 能夠經過鏈式調用實現異步編程。

2.async 函數和 Generator 函數就比較有意思了,async 函數就是將 Generator 函數的星號(*)替換成 async,將 yield 替換成 await,並內置執行器,僅此而已。

3.不難發現,async 的寫法更具語義化,而且更加清晰。

5、使用注意事項

1.await 命令後面的 Promise 對象,運行結果多是 rejected,因此最好把 await命令放在 try...catch 代碼塊中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另外一種寫法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}
複製代碼

2.多個 await 命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發。

function getA () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('A')
    }, 1000)
  })
}

function getB () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('B')
    }, 1000)
  })
}

async function myAsync () {
  let A = await getA();
  console.log('A: ', A)
  let B = await getB();
  console.log('B: ', B)
}

myAsync()
複製代碼

上面代碼中,getA 和 getB 是兩個獨立的異步操做(即互不依賴),被寫成繼發關係。這樣比較耗時,由於只有 getA 完成之後,纔會執行 getB,徹底可讓它們同時觸發。

// 寫法一
async function myAsync () {
  let [A, B] = await Promise.all([getA(), getB()])
  console.log('A: ', A)
  console.log('B: ', B)
}

myAsync()
複製代碼
// 寫法二
async function myAsync () {
  let aPromise = getA()
  let bPromise = getB()
  let A = await aPromise
  let B = await bPromise
  console.log('A: ', A)
  console.log('B: ', B)
}

myAsync()
複製代碼

上面兩種寫法,getA 和 getB 都是同時觸發,這樣就會縮短程序的執行時間。

3.await 命令只能用在 async 函數之中,若是用在普通函數,就會報錯。

6、小結

函數前面的關鍵字 async 有兩個做用:

1.讓這個函數返回一個 promise 2.容許在函數內部使用 await,這個 await 關鍵字又讓 JavaScript 引擎等待直到 promise 完成,若是有錯誤,就會拋出異常,不然,就返回結果。

這兩個關鍵字一塊兒用就提供了一個通俗易懂的方式來控制異步編程,而且易於讀寫。

7、附加題:async、promise、setTimeout 的執行順序

相信你對 Promise、Generator、async 已經有了必定的瞭解了,若加上 setTimeout,你對代碼的執行順序還很清晰嗎?

咱們來看一道寫出執行結果的題,相信不少同窗面試的時候都遇到過,是否是很懵逼!!!

async function async1() {
   console.log('async1 start')
   await async2()
   console.log('async1 end')
}

async function async2() {
   console.log('async2')
}

console.log('script start')

setTimeout(() => {
	console.log('setTimeout')
},0)

async1()

new Promise((resolve) => {
	console.log('promise1')
	resolve()
}).then(() => {
	console.log('promise2')
})

console.log('script end')
複製代碼

執行結果(不一樣瀏覽器執行結果可能不一樣,下面結果用的谷歌):

請你們謹記執行規則:setTimeout 的優先級最低,沒有 async 和 promise 級別高(其實 async 和 promise 是同樣的,由於調用 async 方法時就是返回一個 promise 對象),async 和 promise 的 .then 就看誰先進入到的任務隊列裏面,任務隊列裏面有先進先出的概念。

按照這個規則,相信你很快就能寫出執行結果了。

具體詳解步驟在這裏

相關文章
相關標籤/搜索