zoukankan      html  css  js  c++  java
  • ES6:async / await ---使用同步方式写异步代码

    前言

    最近博主在看异步编程的实现方法,从 Promise对象 到 Gerenator函数真的是头大,会想真的要写这么复杂的代码吗?
    回答:当然不会。当我学到async和await的时候才知道原来 Promise对象 和 Gerenator函数都是为它做的铺垫。
    博主建议如果你还不了解什么是异步编程可以去看看JavaScript异步编程的四种方法,看完以后可以看Promise对象Generator函数的异步应用
    这篇文章会让你以同步的方式来写异步代码,真的很赞。

    概述

    在弄清楚什么是async和await之前,你需要先知道一个底层技术。我们需要讲解Generator的底层的实现机制---协程,带你一步一步来弄懂async/await的工作方式。

    协程

    协程是一种比线程更加轻量级的存在。不了解进程与线程的关系的可以先看这篇文章:进程与线程:形象而不抽象
    协程与线程的关系就好像是线程与进程的关系,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。

    • 比如说,当前执行的是A协程,要启动B协程,那么A协程就需要将主线程的控制权交给B协程,这就体现在A协程暂停执行,B协程恢复执行;
    • 同样从B协程到A协程也是如此。通常,如果从A协程启动B协程,我们就把A协程称为B协程的父协程。
      协程其实不是被操作系统内核所管理的,而完全是由程序控制的。这样带来的好处是性能得到了很大的提升,不会被线程切换那样消耗资源。
      为了让你更好的理解协程是怎么执行的,我们结合一段代码来理解协程的规则:
    function* genDemo() {
            console.log('开始执行第一段')
            yield 'generator 2'
    
            console.log('开始执行第二段')
            yield 'generator 2'
    
            console.log('开始执行第三段')
            yield 'generator 2'
    
            console.log("执行结束")
            return 'generator 2'
        }
    
    1    console.log('main 0')
    
    2    let gen = genDemo()
    3    console.log(gen.next().value)
    4    console.log('main 1')
    5    console.log(gen.next().value)
    6    console.log('main 2')
    7    console.log(gen.next().value)
    8    console.log('main 3')
    9    console.log(gen.next().value)
    10    console.log('main')
    
    • 对于上面的程序,只有一个父协程在主线程上执行,所以开始会打印第一行代码 main 0
    • 接着let gen = genDemo() 会创建一个gen协程,创建之后gen协程并没有立即执行。要让gen执行,需要调用gen.next。
    • 接着第三行代码中执行gen.next(),这时父协程暂定执行,协程切换到gen协程执行,所以打印“开始执行第一段”
    • 接着遇到yield关键字,gen协程停止执行,并把yield关键字后面的内容返回给父协程,父协程恢复执行... , 如果协程在执行期间,遇到return关键字,那么JavaScript引擎会结束当前协程,并把return关键字后面的内容返回给父协程。

    小结

    1.gen协程和父协程是在主线程上交互执行的,并不是并发执行的,它们之间切换是通过yield和gen.next来配合完成的。
    2.当在gen协程中调用yield方法时,JavaScript引擎会保存gen协程的当前调用栈信息,并恢复父协程的调用栈信息。同样,当在父协程中执行gen.next时,JavaScript引擎会保存父协程的调用栈信息,并恢复gen协程的调用栈信息。读到这里,相信你已经了解生成器(generator)协程是怎么工作的了吧。

    async/await

    为了使用更加直观简洁的代码来异步编程,就要学习async和await的工作原理。

    async

    根据MDN定义,async是一个通过异步执行隐式返回Promise作为结果的函数。
    我们先看看它是怎么隐式返回Promise的,异步执行一会在解释。

    async function foo(){
            return 2
        }
        console.log(fool()) // Promise{<resolved>:2}
    

    执行这段代码,我们可以看到调用async声明的foo函数返回一个Promise对象,状态是resolved。

    await

    我们知道async函数返回的是一个Promise对象,那么下面我们再结合一段代码来解释await是什么。

    1    async function foo() {
    2        console.log(1)
    3        let a = await 100
    4        console.log(a)
    5        console.log(2)
    6    }
    7    console.log(0)
    8    foo()
    9    console.log(3)
    

    你能判断出打印出来的内容是什么吗?
    我们先站在协程的视角来看看这段代码的整体执行情况:

    • 首先执行第7行代码打印迟来0,
    • 紧接着就是执行foo函数,由于foo函数是被saync标记过的,所以当进入该函数的时候,JavaScript引擎会保存当前的调用栈等信息,然后执行foo函数中的console.log(1)语句,并打印出1。
    • 紧接着就是执行foo函数中的await 100这个语句了,这里是我们分析的重点,因为在执行await 100这个语句时,JavaScript引擎会在背后为我们默默做了太多的事情,那么下面我们就把这个语句拆开,来看看那JavaScript到底做了什么事情。

    当执行到await 100时,会默默创建一个Promise对象,代码如下:

    let promise = new Promise((resolve,reject){
          resolve(100)
    })
    
    • JavaScript引擎会把resolve(100)这个任务提交给微任务队列。如果你还不知道什么是微任务,请看宏任务与微任务
    • 然后JavaScript引擎会暂停当前协程的执行,将主线程的控制权交给父协程执行,同时会将promise对象返回给父协程。
    • 这时候父协程会调用promise.then来监控promise状态的变化。
    • 接下来继续执行父协程的流程,执行第9行代码,打印3。
    • 随后,父协程即将执行结束
    • 在结束之前,会进入微任务检查点,然后执行微任务队列,微任务队列中有resolve(100)的任务等待执行,执行到这里,会触发promise.then中的回调函数,回调函数被激活后,将主线程的控制权交给foo协程,并将value值传给foo函数协程,然后foo函数协程把value的值赋给变量a,然后foo协程继续执行后面的语句,执行完成以后,会把控制权归还给父协程。
      以上就是await/async的执行流程。正是因为async和await在背后为我们做了大量的工作,所以才可以能用同步的方式写出异步代码来。

    总结

    1.使用async和await可以实现用同步代码的风格来编写异步代码。这是因为async/awiat的基础技术使用了生成器和Promise。
    2.另外v8引擎还为async/await做了大量的语法层面的包装。

    思考题

    留个代码供大家思考,你能分析出这段代码执行后输出的内容吗?

        async function foo() {
            console.log('fool')
        }
        async function bar(){
            console.log('bar start')
            await foo()
            console.log('bar end')
        }
        console.log('script start')
        setTimeout(function(){
            console.log('setTimeout')
        },0)
        bar()
        new Promise(function(resolve,reject){
            console.log('promise executor')
            resolve()
        }).then(function(){
            console.log('promise then')
        })
        console.log('script end')
    

    欢迎在评论区分享你的答案。

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    通过电脑chrome调试手机真机打开的微信H5页面,调试电脑微信H5页面
    关于神策埋点数据采集
    jmeter控制仅一次登录的三种方案
    win10下mysql8.0.19解压版的安装教程
    mysql中的case when then 的用法
    python+openpyxl的excel的相关读写
    使用Gitlab-CI 实现NetCore项目Docker化并部署到阿里云K8S
    NetCore 中间件获取请求报文和返回报文
    WebApi 通过拦截器设置特定的返回格式
    NetCore AutoMapper的封装
  • 原文地址:https://www.cnblogs.com/XF-eng/p/14154464.html
Copyright © 2011-2022 走看看