美文网首页大前端开发
前端异步中的async/await

前端异步中的async/await

作者: iliuqiang | 来源:发表于2019-08-29 14:21 被阅读0次

    在前端开发中,经常会遇到异步的情况。异步操作一直是JS中不可或缺的一环。从最开始使用回调函数实现异步操作,到后面的Promise,再到ES2017引入的async函数,异步操作逐渐进化,变得越来越简单方便,接下来就仔细看看在ES2017引入了async函数后,异步操作产生了哪些变化。

    通常情况下,使用异步函数,都是async/await一起用的,但下面分别讲解async和await在异步函数中到底有什么用。

    1、单独使用async:

    通常情况下使用async命令是因为函数内部有await命令,因为await命令只能出现在async函数里面,否则会报语法错误,这就是为什么async/await成对出现的原因,但是如果对一个普通函数单独加个async会是什么结果呢?来看个例子:

    async function test () {
      let a = 2
      return a
    }
    const res = test()
    console.log(res)
    
    使用async的函数返回的结果.png

    由例子可以看成,async函数返回的是一个Promise对象,如果函数中有返回值,async会把这个返回值通过Promise.resolve()封装成Promise对象,要取这个值也很简单,直接通过then()就能取出,代码如下:

    res.then(a => {
      console.log(a) // 2
    })
    

    2、单独使用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函数的调用并不会阻塞。

    3、解决了什么问题:

    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的调用更加清晰简单,和同步代码一样。

    4、带来了什么问题:

    使用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 ()
    

    上面代码继发执行,所花时间是:

    实际上,这段代码执行顺序,我并不关心,继发执行就浪费大量执行时间,下面改成并发执行:

    function call () {
      Promise.all([test(), test1(), test2()])
    }
    call()
    

    所花时间:
    因此在使用async/await时需要特别注意这一点。

    4、循环中的小问题:

    在写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()
    

    上面这种写法是继发式的,也就是会等前面一个任务执行完,再执行下一个,但是也许你并不关心执行过程,只要拿到想要的结果就行了,这时并发式的效率会更高,来看代码:

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

    相关文章

      网友评论

        本文标题:前端异步中的async/await

        本文链接:https://www.haomeiwen.com/subject/upqdectx.html