Async/Await是這樣簡化JavaScript代碼的

譯者按:Async/Await替代Promise的6個理由中,咱們比較了兩種不一樣的異步編程方法:Async/AwaitPromise,這篇博客將經過示例代碼介紹Async/Await是如何簡化JavaScript代碼的。javascript

爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。java

Async/Await是JavaScript的ES7新特性,來源於**.NETC#。它能夠不用回調函數,像同步代碼那些編寫異步代碼。這篇博客將經過一些代碼示例,來講明Async/Await**如何簡化JavaScript代碼。node

1. 去除回調函數

運行本文的示例代碼,並不須要額外的函數庫。對於最新版的主流瀏覽器中,例如Chrome,Firefox, Safari以及Edge,它們都支持Async/Await語法。另外,Node.js 7.6+也支持了Async/Await語法。編程

咱們編寫了一些簡單的API接口,用於模擬異步操做。這些接口都返回Promise,並在200ms後resolve一些數據。api

class Api {
  constructor () {
    this.user = { id: 1, name: 'test' }
    this.friends = [ this.user, this.user, this.user ]
    this.photo = 'not a real photo'
  }

  getUser () {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(this.user), 200)
    })
  }

  getFriends (userId) {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(this.friends.slice()), 200)
    })
  }

  getPhoto (userId) {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(this.photo), 200)
    })
  }

  throwError () {
    return new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('Intentional Error')), 200)
    })
  }
}

嵌套Promise

function callbackHell () {
  const api = new Api()
  let user, friends
  api.getUser().then(function (returnedUser) {
    user = returnedUser
    api.getFriends(user.id).then(function (returnedFriends) {
      friends = returnedFriends
      api.getPhoto(user.id).then(function (photo) {
        console.log('callbackHell', { user, friends, photo })
      })
    })
  })
}

曾經使用Promise編寫回調函數的開發者必定不會陌生,這樣一層層的嵌套代碼一般是這樣結尾的:數組

})
    })
  })
}

在回調函數中調用回調函數,一層層地嵌套,這就是所謂的「回調地獄」。在真實的代碼中,這樣的狀況並很多見,一般更爲複雜。promise

鏈式Promise

function promiseChain () {
  const api = new Api()
  let user, friends
  api.getUser()
    .then((returnedUser) => {
      user = returnedUser
      return api.getFriends(user.id)
    })
    .then((returnedFriends) => {
      friends = returnedFriends
      return api.getPhoto(user.id)
    })
    .then((photo) => {
      console.log('promiseChain', { user, friends, photo })
    })
}

Promise的最佳特性之一,就是能夠在then回調函數中,return一個新的Promise,這樣就能夠將這些Promise連接起來,只有一層嵌套。鏈式Promise嵌套Promise簡單不少,可是仍是不少冗餘。瀏覽器

Async/Await

不使用回調函數能夠嗎?固然能夠!使用Async/Await的話,7行代碼就能夠搞定。併發

async function asyncAwaitIsYourNewBestFriend () {
  const api = new Api()
  const user = await api.getUser()
  const friends = await api.getFriends(user.id)
  const photo = await api.getPhoto(user.id)
  console.log('asyncAwaitIsYourNewBestFriend', { user, friends, photo })
}

使用await關鍵詞時,賦值操做將等到異步操做結束時才進行。這樣,看起來與同步代碼無異,實際執行事實上是異步的。異步

2. 簡化循環

Async/Await可讓一些複雜操做,好比循環變得簡單。例如,當咱們須要獲取某個user的全部friends的friends列表,應該怎樣操做呢?

使用Promise

function promiseLoops () {  
  const api = new Api()
  api.getUser()
    .then((user) => {
      return api.getFriends(user.id)
    })
    .then((returnedFriends) => {
      const getFriendsOfFriends = (friends) => {
        if (friends.length > 0) {
          let friend = friends.pop()
          return api.getFriends(friend.id)
            .then((moreFriends) => {
              console.log('promiseLoops', moreFriends)
              return getFriendsOfFriends(friends)
            })
        }
      }
      return getFriendsOfFriends(returnedFriends)
    })
}

咱們使用了遞歸函數getFriendsOfFriends來獲取friends-of-friends,知道friends數組爲空。如此簡單的任務,這樣寫顯然過於複雜了。

使用**Promise.all()**來實現的話,則並不是循環,而是併發執行。

使用Async/Await

This could be so much easier.

async function asyncAwaitLoops () {
  const api = new Api()
  const user = await api.getUser()
  const friends = await api.getFriends(user.id)

  for (let friend of friends) {
    let moreFriends = await api.getFriends(friend.id)
    console.log('asyncAwaitLoops', moreFriends)
  }
}

這時,能夠直接使用for循環來實現,很是簡單。

3. 簡化併發

使用循環逐個獲取friends-of-friends顯然太慢,採用併發方式更爲簡單。

async function asyncAwaitLoopsParallel () {
  const api = new Api()
  const user = await api.getUser()
  const friends = await api.getFriends(user.id)
  const friendPromises = friends.map(friend => api.getFriends(friend.id))
  const moreFriends = await Promise.all(friendPromises)
  console.log('asyncAwaitLoopsParallel', moreFriends)
}

爲了實現併發,只須要將Promise數組做爲Promise.all()的參數便可。這樣,只須要await一個Promise,而這個Promise會在全部併發操做結束時resolve

4. 簡化錯誤處理

使用回調函數處理Promise錯誤

function callbackErrorHell () {
  const api = new Api()
  let user, friends
  api.getUser().then(function (returnedUser) {
    user = returnedUser
    api.getFriends(user.id).then(function (returnedFriends) {
      friends = returnedFriends
      api.throwError().then(function () {
        console.log('Error was not thrown')
        api.getPhoto(user.id).then(function (photo) {
          console.log('callbackErrorHell', { user, friends, photo })
        }, function (err) {
          console.error(err)
        })
      }, function (err) {
        console.error(err)
      })
    }, function (err) {
      console.error(err)
    })
  }, function (err) {
    console.error(err)
  })
}

這樣作很是糟糕,代碼很是冗餘,可讀性也不好。

使用catch方法處理Promise錯誤

function callbackErrorPromiseChain () {
  const api = new Api()
  let user, friends
  api.getUser()
    .then((returnedUser) => {
      user = returnedUser
      return api.getFriends(user.id)
    })
    .then((returnedFriends) => {
      friends = returnedFriends
      return api.throwError()
    })
    .then(() => {
      console.log('Error was not thrown')
      return api.getPhoto(user.id)
    })
    .then((photo) => {
      console.log('callbackErrorPromiseChain', { user, friends, photo })
    })
    .catch((err) => {
      console.error(err)
    })
}

這樣處理好多了,僅僅須要在Promise鏈的最後,使用catch方法處理全部錯誤。

使用Try/Catch處理Async/Await錯誤

async function aysncAwaitTryCatch () {
  try {
    const api = new Api()
    const user = await api.getUser()
    const friends = await api.getFriends(user.id)

    await api.throwError()
    console.log('Error was not thrown')

    const photo = await api.getPhoto(user.id)
    console.log('async/await', { user, friends, photo })
  } catch (err) {
    console.error(err)
  }
}

對於Async/Await代碼,使用Try/Catch便可處理,和同步代碼同樣,更加簡單。

如何你須要監控線上JavaScript代碼的錯誤時,能夠無償使用Fundebug的實時錯誤監控服務,只須要一行代碼就能夠搞定!

5. 簡化代碼組織

使用async關鍵詞定義的函數都會返回Promise,這樣能夠更方便地組織代碼。

例如,在以前的示例中,咱們能夠將獲取的user信息return,而不是直接打印;而後,咱們能夠經過返回的Promise來獲取user信息。

async function getUserInfo () {
  const api = new Api()
  const user = await api.getUser()
  const friends = await api.getFriends(user.id)
  const photo = await api.getPhoto(user.id)
  return { user, friends, photo }
}

function promiseUserInfo () {
  getUserInfo().then(({ user, friends, photo }) => {
    console.log('promiseUserInfo', { user, friends, photo })
  })
}

使用Async/Await語法,則更加簡單:

async function awaitUserInfo () {
  const { user, friends, photo } = await getUserInfo()
  console.log('awaitUserInfo', { user, friends, photo })
}

如何獲取多個user的信息?

async function getLotsOfUserData () {
  const users = []
  while (users.length < 10) {
    users.push(await getUserInfo())
  }
  console.log('getLotsOfUserData', users)
}

如何併發?如何處理錯誤?

async function getLotsOfUserDataFaster () {
  try {
    const userPromises = Array(10).fill(getUserInfo())
    const users = await Promise.all(userPromises)
    console.log('getLotsOfUserDataFaster', users)
  } catch (err) {
    console.error(err)
  }
}

版權聲明:

轉載時請註明做者Fundebug以及本文地址:

https://blog.fundebug.com/2017/10/16/async-await-simplify-javascript/

相關文章
相關標籤/搜索