我們為什麼需要async/await ?

芬達Tz發表於2019-03-25

我們為什麼需要async/await ?
內有多隻貓咪 ,慎入。

async 是什麼 & async的基本用法

async function 宣告用於定義一個返回 AsyncFunction 物件的非同步函式。非同步函式是指通過事件迴圈非同步執行的函式,它會通過一個隱式的 Promise 返回其結果。但是如果你的程式碼使用了非同步函式,它的語法和結構會更像是標準的同步函式。 引用自MDN。

js的方法和語法糖多數都是語義化的,從字面意思上來說,async代表非同步的,用來表示一個非同步的函式,返回一個promise,可以使用then方法新增回撥。 可以看下這個例子:

 const foo = async () => {
  return { name: '芬達' }
 }
 console.log(foo())
複製程式碼

我們為什麼需要async/await ?
可以看到返回的是promise哦。這樣不就可以愉快的寫then式回撥了嘛。偷笑! 簡單來說,只要使用了async, 就會返回一個promise

我們為什麼需要async/await ?
貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/

await 與 async 搭配的基本用法

await 在等誰

await在英漢詞典中是動詞 等候 的意思,用法如下:

// 只能用在async函式中
const val = await promise
複製程式碼

await可以讓js進行等待,直到promise執行並返回結果時才會繼續往下執行。可以看一個小例子:

const foo2 = async () => {
   const promise = new Promise((reslove, reject) => {
       setTimeout(() => {
         reslove('芬達')
       }, 1000)
    })
    const ret = await promise
    console.log(ret)
}
foo2()
複製程式碼

上面程式碼會在1s後列印芬達
從上面程式碼來說,await在等一個承諾,好吧,是promise,更嚴謹的來說,他是在等待一個表示式,只不過這個表示式可以是promise,也可以是其他任意表示式的結果。所以,下面的程式碼是完全可以執行的:

const foo3 = () => '芬達'
const foo4 = async () => {
    const ret = await foo3()
    console.log(ret)
}
foo4()
複製程式碼

當然也是列印的芬達

await 等到了之後會做什麼

通俗點說,你可以把await看做是一個運算子號,如果它等到的不是promise,那麼他的運算結果就是當前它所等到的值,如果等到的是promise,那麼他會臨時阻塞後續程式碼,直到promise物件resolve,取到 resolve 的值,將其作為運算結果返回。
emmmmm, 就是這樣。

我們為什麼需要async/await ?
貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/

async 和 promise 的聯絡

async 可以看做是promiseGenerator的語法糖,但對其做了改進。

  • 內建執行器,Generator的執行必須依靠執行器,但async的執行器與生俱來,使得async函式與普通函式的呼叫別無二致。
  • 更好的語義化,就不做解釋了吧
  • 返回值是promise,對開發者友好,感覺這個才是最重要的有木有。

我們為什麼需要async/await ?
貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/

它能為我們做什麼?

這個問題的答案應該是毋庸置疑的,必然是為了解決回撥地獄。async/await的優勢就在於處理then式呼叫鏈呢。 我們可以先假設一個場景: 使用者登入之後拿到userId,然後在去呼叫介面拿到token,最後在其他介面的請求頭裡新增上token欄位.....別問為什麼這麼白痴的設計,假設而已

初級前端的寫法:

 ajax('login', { username, password }, ({ userId }) => {
    ajax('getToken', { userId }, ({ token }) => {
      ajax('getOtherInfo', { token }, res => {
        // do something...
      })
    })
  })
複製程式碼

是不是頭皮發麻,當然,如果你是寫這樣的程式碼的人,你可能覺得還可以接受,如果你是維護這樣的程式碼的,你就會明白有多難維護,這裡只寫了三層,實際中甚至更多。意味著層級巢狀,牽一髮而動全身,要改都得改的死衚衕,意味著程式碼縮排都能噁心壞你,所以,初級大圓滿前端是如何寫的呢?

ajax('login', { username, password })
    .then(({ userId }) => ajax('getToken', { userId }))
    .then(({ token }) => ajax('getOtherInfo', { token }))
    .then(res => {
    // do something...
    })
複製程式碼

這樣一來,利用鏈式then呼叫,既可以清晰的展現api依賴關係,又可以優雅的縮排程式碼,但我們是講async的啊老鐵,所以還是看下吧asynv大法。

 async function foo({ username, password }) {
    const { userId } = await ajax('login', { username, password })
    const { token } = await ajax('getToken', { userId })
    const res = await ajax('getOtherInfo', { token })
    // do something ...
  }
複製程式碼

上面有提到,await會臨時阻塞後續程式碼的執行,這就代表著介面呼叫會按照我們的程式碼順序"同步"執行(說是同步,實際上還是非同步請求,表現為同步行為是語法糖的原因)。這樣,程式碼就會按照我們的預期執行,介面依賴關係也更加明瞭,維護起來也是賞心悅目的。

我們為什麼需要async/await ?

貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/

它的優點和缺陷

上面有談到他一部分的優點,其實還有如下優點

  • 語法簡潔,使程式碼可讀性更高
  • 能使用try catch捕獲異常
  • 使程式碼更加符合思維邏輯。

至於缺點呢...

  • 需要babel編譯
  • 缺少abort請求中斷,缺少非同步控制流程。
  • 異常捕獲較為麻煩
  • 沒有依賴關係的請求需要藉助promise.all

我們為什麼需要async/await ?

貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/

如何優雅的在async/await中處理錯誤

理論上來說,await等到的可能是promise.reject,這種情況可以使用try/catch來捕獲異常,ok沒毛病,像下面這樣


 try {
    const { userId } = await ajax('login', { username, password })
 } catch {
     throw new Error('no user found')
 }
複製程式碼

But,如果像上面一樣有很多個await呢,怎麼辦,每次都要寫一下嘛,這樣豈不是很難受?上週在沸點看到一位大佬對promise的異常處理,很有意義。如圖

我們為什麼需要async/await ?
, 同樣的啊,async也是可以藉助promise來實現統一的異常捕獲。

function util(promise) {
  return promise.then(data => [null, data]).catch(err => [err])
}
async function foo({ usernam, password }) {
  let userId, token, err
  ;[err, { userId }] = await util(ajax('login', { username, password }))
// 因預設資料返回是物件,所以加了解構,部分省略...
// do something
}
複製程式碼

沒有大佬的考慮全面,但也足以應付日常需求了

我們為什麼需要async/await ?
貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/

簡單總結

async/await總的來說,是一個優秀的非同步解決方案,利大於弊,值得一用。

因為我平時用async的頻率很低,所以專門總結了這篇文字。如果有什麼錯誤還請各位大佬指正。

參考

Async +Await
精讀 Async/Await 更優越的 6 大理由

相關文章