zoukankan      html  css  js  c++  java
  • Async/Await是这样简化JavaScript代码的

    译者按: 在Async/Await替代Promise的6个理由中,我们比较了两种不同的异步编程方法:Async/AwaitPromise,这篇博客将通过示例代码介绍Async/Await是如何简化JavaScript代码的。

    为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

    Async/Await是JavaScript的ES7新特性,来源于.NETC#。它可以不用回调函数,像同步代码那些编写异步代码。这篇博客将通过一些代码示例,来说明Async/Await如何简化JavaScript代码。

    1. 去除回调函数

    运行本文的示例代码,并不需要额外的函数库。对于最新版的主流浏览器中,例如Chrome,Firefox, Safari以及Edge,它们都支持Async/Await语法。另外,Node.js 7.6+也支持了Async/Await语法。

    我们编写了一些简单的API接口,用于模拟异步操作。这些接口都返回Promise,并在200ms后resolve一些数据。

    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在回调函数中调用回调函数,一层层地嵌套,这就是所谓的“回调地狱”。在真实的代码中,这样的情况并不少见,通常更为复杂。

    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 })
        })
    }

    Async/AwaitPromise的最佳特性之一,就是可以在then回调函数中,return一个新的Promise,这样就可以将这些Promise链接起来,只有一层嵌套。链式Promise嵌套Promise简单很多,但是还是很多冗余。

    不使用回调函数可以吗?当然可以!使用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 })
    }

    2. 简化循环

    使用await关键词时,赋值操作将等到异步操作结束时才进行。这样,看起来与同步代码无异,实际执行事实上是异步的。

    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)
        })
    }

    使用Promise.all()来实现的话,则并非循环,而是并发执行。我们使用了递归函数getFriendsOfFriends来获取friends-of-friends,知道friends数组为空。如此简单的任务,这样写显然过于复杂了。

    使用Async/Await

    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)
      }
    }

    如何你需要监控线上JavaScript代码的错误时,可以免费使用Fundebug的实时错误监控服务,只需要一行代码就可以搞定!对于Async/Await代码,使用Try/Catch即可处理,和同步代码一样,更加简单。

    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

    Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。
    自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。欢迎免费试用!

     

  • 相关阅读:
    tcprstat分析服务的响应速度
    mysqldump之不老将
    Java数据持久层框架 MyBatis之API学习二(入门)
    Java数据持久层框架 MyBatis之API学习一(简介)
    插入数据库日期格式转换
    eclipse出现错误:he type java.util.Map$Entry cannot be resolved. It is indirectly referenced
    注释中不允许出现字符串 "--"。
    mybatis if条件查询 及<号的问题
    This is probably because there is no OLE editor registered against the type of file you were trying to open.
    github not authorized eclipse
  • 原文地址:https://www.cnblogs.com/fundebug/p/7683421.html
Copyright © 2011-2022 走看看