讲讲异步函数async/await

1,860 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

一、async 和 await 的作用

ES8新增了 async/await 提供了异步编程解决方案

1. async

async用来声明异步函数,使用async关键字可以让函数具有异步特征,但是在总体上代码依然是同步行为,与普通函数没有区别,但是在返回值上与普通函数不同。

  1. 代码示例
  async function foo(){
    return ''
  }
   const result = foo()
   console.log(result)
  1. 打印结果 B1C9BA3C-85F1-421D-9A3F-CAB6E3D6B442.png

  2. 小结

  • 从上面打印结果可以看出来被async修饰的函数返回的是一个promise对象,并且是成功状态
  • async声明的函数直接return结果数据,async会把这个返回的数据通过Promise.resolve()封装成Promise对象
  • 当然,如果async声明的函数return为空的话,返回的对象为undefined

2. await

因为异步函数主要针对不会马上完成的任务,所以需要await关键字来暂停异步代码的执行,需要注意的是await会暂停执行异步函数后面的代码。

3. 关于async和await的综合使用

async/await在使用的时候起作用的是await。因为async只是让函数具有异特征,它并不会去改变代码的执行,await才是关键。就如同开始就讲的,没有await,async修饰的函数就是普通函数,明白这一点很重要。

  1. 代码示例
    //async修饰
    async function foo1(){
     console.log('foo1')
    }
    
    //async/await同时用
    async function foo2(){
     console.log(await Promise.resolve('foo2'))
    }

    //普通函数
    function foo3(){
     console.log('foo3')
    }
    foo1()
    foo2()
    foo3()
  1. 打印结果

A9A523EE-28E3-4997-9F5D-C262647FA275.png

  1. 小结 从上面的打印顺序就能看出来,被async修饰的函数第一个调用第一个执行,async/await同时修饰的则是暂停执行。

4. 关于await的进一步拓展

await也并不是完全等待,也不是说函数内部只要有await,整个函数或者在之后的函数都会暂停,而是等await后面的值什么时候可以用了,JavaScript运行时会向消息队列推送一个任务,这个任务会恢复异步函数的执行。在函数里没有await修饰的位置,就还是同步行为。这一整个流程关乎同步异步问题,通过一个简单的例子来看看这一点。

  1. 代码示例
   //被async修饰
   async function foo(){
     console.log(2)
     await null
    console.log(4)
    }
    
    console.log(1)
    
    foo()
    
    console.log(3)
  1. 打印结果

A14363DD-7298-4581-8053-4FFD6A75DBBD.png

  1. 小结 同步打印出1,调用函数输出2await null导致函数退出然后等3打印最后才打印4.所以也能看出来即使是立即输出4,也会被暂停,只有被await修饰后的代码才能被暂停。

二、任务队列

通过了解了async、await的前置知识后,再来说说任务队列,这样会比较好理解。

1. 关于任务队列,你必须要知道的

  1. JS是单线程编程语言,同一时间只能做同一件事情
  2. JS分为同步任务和异步任务
  3. 同步会阻塞代码执行,异步不会阻塞代码执行
  4. 任务又分为宏任务和微任务,而他们分别有相对应的队列:宏任务队列(macro tasks)和微任务队列(micro tasks)而宏任务队列可以有多个,微任务队列只能有一个

宏任务、微任务

  • 宏任务
    • script(全局任务), setTimeout, setInterval, Ajax, DOM事件监听,setImmediate(Node), I/O, UI rendering.
  • 微任务
    • Promise.then,async/await,mutationobserver(H5),process.nextTick(Node)

2. 具体执行机制

具体的执行还得看代码验证一下

1. 代码

下述打印题共输出8个结果,可以试着输出一下

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

      console.log('script start')

     setTimeout(function() {
        console.log('setTimeout')
     }, 0)

     async1()

     new Promise(function(resolve) {
        console.log('promise1')
        resolve()
      }).then(function() {
        console.log('promise2')
     })
     console.log('script end')

2. 分析

  1. 首先从上往下开始也就是事件循环从宏任务开始,打印 script start
  2. 遇到 setTimeout,其作为一个宏任务源,则会先将其任务分发到对应的队列中
  3. 接下来调用函数 async1(),进入async1,首先打印 async1 start,遇到了await时,会将await后面的表达式执行一遍,所以就紧接着输出async2,而await后面的打印console.log('async1 end')则会被异步处理,加入到Promise的微队列中
  4. 接着跳出函数继续往下执行后面的代码,遇到new Promise,因为async/awaitnew Promise的语法糖,async相当于new Proimise所以Promise中的函数是立即执行的,所以接着打印promise1,而await相当于.then(),所以.then()加入微任务的异步队列
  5. 跳出函数,接着执行完console.log('script end')全局任务也就执行完了
  6. 当执行完一个宏任务之后都会去检查里面是否有微任务未执行,所以开始清空微队列,按顺序输出微队列里面的async1 end,promise2,setTimeout
  7. 当微任务全部执行完毕之后,就会开始执行下一个宏任务,而下一个宏任务是setTimeout,只有他一个,故此输出setTimeout,至此,整个流程全部结束

3. 打印结果如下

AC89D787-8ABF-4FE1-9710-E9ED1BBBCD13.png

好了,以上就是本篇文章的分享,感谢阅读!