聊聊async/await原理

news2024/9/30 23:34:20

前言

我们知道Promise的出现极大地解决了回调地狱,但是如果使用流程非常复杂的话,就非常容易过多地调用Promise的then()方法,这样也不利于使用和阅读。

例如:我希望在请求 www.baidu.com 后输出请求的结果,再去请求 www.taobao.com 后再输出请求结果,如果只用 Promise 实现,那么代码就是下面的样子:

fetch("https://www.baidu.com").then(res => {
    console.log(res)
    return fetch("https://www.taobao.com")
}).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

这只是去请求两个网站,如果流程一旦多了,那么then()方法的调用也是随之增加的,虽然整个过程较为线性,但是代码的阅读性依然很差。所以,在es7中提出了async/await来让我们能够用同步的方式来实现异步,但是要记住async/await只是一个语法糖,其本质依然还是异步的,不过是让我们可以用同步的方式来书写而已。我们可以看一下下面的代码,同样是解决上面的问题:

async function foo() {
    try {
        const result1 = await fetch("https://www.baidu.com")
        console.log(result1)
        const result2 = await fetch("https://www.taobao.com")
        console.log(result2)
    } catch (err) {
        console.log(err)
    }
}

使用 async/await让代码的书写就和同步一样,并且还可以使用try catch 来捕获错误。试问一下,这两种书写方式,对于程序员来说会选择哪一个呢?

原理

在我看来,async/awati的使用就好像是把函数暂停了一样,当执行到await时,函数暂停执行,直到await等待的Promise状态改变了,才会回到这个函数执行。这种方式是不是很眼熟?是的,这简直就跟生成器的工作方式一模一样,所以要弄懂async/await的原理前,我们要先了解一下生成器是如何工作的。不过在了解生成器之前,我先介绍一下协程这个概念。

协程

协程是一种比线程更轻量级的存在,它不由CPU直接调度,而是在用户态可以通过程序来操纵。你可以理解为协程是跑在线程上的任务,一个线程上可以有多个协程,但是一个线程同时只能执行一个协程。比如,当前在主线程上执行的是A协程,如果这个时候要启动B协程,A协程就需要将主线程的控制权让出来,并且暂停执行,B协程开始在主线程上运行。同样的,在B协程中也可以启动A协程。通常,如果在A协程中启动B协程,我们把A协程称作B协程的父协程。

协程带来的好处就是可以提升性能,协程的切换并不会像线程切换那样过多地消耗资源。了解了协程的存在,我们就可以知道为什么会有生成器了。

生成器

什么是生成器?就是前面带有*的一个函数,这个函数可以暂停执行或者恢复执行。在函数内部可以使用yield关键字来暂停函数的执行,切换到外部函数,其实就是协程的切换。在外部函数中可以通过调用next()方法来恢复函数的执行。

function * bar() {
    console.log('step 1')
    yield 1
    console.log('step 2')
    yield 2
    console.log('step 3')
    yield 3
}

const gen = bar()
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

上述代码分布执行

  1. 执行生成器bar,此时主线程执行bar函数协程,打印step1。
  2. 执行到yield关键字,bar函数协程暂停执行,转到外部函数协程,并返回yield关键字抛出的值;
  3. 外部函数协程执行next()方法,该方法的返回值的value属性值就是yield抛出的值,打印1,并切换协程,主线程执行bar函数协程;
  4. 打印step2,执行到yield关键字,bar函数协程暂停执行,转到外部函数协程,并返回yield关键字抛出的值;
  5. 外部函数协程执行next()方法,该方法的返回值的value属性值就是yield抛出的值,打印2,并切换协程,主线程执行bar函数协程;
  6. 打印step3,执行到yield关键字,bar函数协程暂停执行,转到外部函数协程,并返回yield关键字抛出的值;
  7. 外部函数协程执行next()方法,该方法的返回值的value属性值就是yield抛出的值,打印3,并切换协程,主线程执行bar函数协程;
  8. bar函数执行完毕,该协程销毁,转到外部函数协程继续执行代码。通过上面生成器与协程的分析,想必 async/await 的原理已经要呼之欲出了,那么让我们来正式开始 async/await 的原理揭秘

async/await

async

async 在MDN上的定义就是一个通过异步执行并隐式并返回一个 Promise 作为结果的函数。我们需要关注两个地方,异步执行和隐式返回Promise,异步执行先放到后面说,对于隐式返回 Promise,我们可以通过代码来一窥究竟。

async function foo() { 
    return 2
}
console.log(foo()) // Promise {: 2}

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

await

async function foo() {
    console.log(1)
    const result = await 2
    console.log(result)
}
console.log(3)
foo()
console.log(4)

在这里插入图片描述
为什么是 3 1 4 2呢?明明是先执行了foo函数再打印的4,为什么4会在2前面呢?这就要提到我们之前说的生成器了,await正是在这基础上做到的。下面我们站在生成器和协程的角度来看下这段代码是如何执行的。

  1. 首先由于foo函数被async标记过,所以当进入该函数的时候,JavaScript引擎会保存当前的调用栈等信息;
  2. 全局执行上下文在主线程上执行,我们在这里暂且将全局执行上下文叫做父协程,首先打印3;
  3. foo函数执行,主线程控制权由父协程转到foo函数协程,打印1;
  4. 执行await 2,在这里做了两件事,第一是新建一个 Promise
 let Promse_ = new Promse((reslove, reject) => {
        resolve(2)
    })

在该Promise的创建中我们看到在执行器中调用了resolve函数,这时JavaScript引擎会将该任务放入微任务队列中。

第二件事就是暂停foo函数的执行,也就是做了yield关键字的事情,切换协程,将主线程的控制权交给父协程,并向父协程返回创建的Promise。

当父协程恢复执行时,会通过调用返回的Promise的then()方法来监听这个Promise的状态,当这个Promise状态改变时会再次切换协程,将主线程控制权交给foo函数协程。我们可以通过下面一段伪代码模拟:

Promise_.then(res => {
    foo().next(res) //因为async返回的是Promise,所以没有next方法,但是内部实现原理是一致的,可以当作参考
})
  1. 父协程在主线程上运行,打印4;
  2. 父协程上的代码都执行完了,到达微任务队列的检查点,发现了微任务队列中有 resolve(2) 需要执行,执行这个任务的时候,会执行该Promise的 then()方法注册的所有回调函数,也就是上述的代码,这时,协程再次切换,主线程上运行foo函数协程,foo函数继续执行,打印2

总结

Promise的出现帮助我们解决了回调地狱,但是也带来了一个问题–then方法的大量使用,同样使得代码不易阅读。而async/await的诞生就是为了让我们通过同步的方式来使用异步,这样极大地简化了我们地代码,使我们代码地易读性大大提高。而 async/await 的实现离不开生成器和协程的概念,正是通过生成器可以自由地切换协程才使得我们可以暂停和恢复一个函数的执行。同样,V8引擎也做了许多事,内部做了大量的语法封装才使得我们能够使用 async/await 语法糖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【基于腾讯云的远程机械臂小车】

1. 项目来源 项目源码地址:https://gitcode.net/VOR234/robot_arm_car/-/blob/master/TencentOS-tiny123.zip https://gitee.com/vor2345/robot_arm_car 程序分别 视频演示:https://www.bilibili.com/video/BV15M4y1D7MD/?vd_source530bf85167de80ff…

46.在ROS中实现global planner(2)

前文实现了一个global planner的模板,并且可以工作,本文将实现astar算法,为后续完成一个astar global planner做准备 1. AStar简介 1.1 AStar Astar算法是一种图形搜索算法,常用于寻路。Astar算法原理网上可以找到很多,简单的说…

企业/品牌新闻稿怎么写?

撰写出优质的企业/品牌新闻稿对于任何一个希望通过新闻媒体推广自己品牌的公司来说都是十分重要的。在新闻稿中,您可以通过介绍自己的公司,披露最新的产品和服务信息以及宣传最新的成就来吸引媒体和读者的关注。下面是一些关于如何撰写出优质的企业/品牌…

【工具篇】Firmwalker车联网实用小工具介绍

前言 firmwalker这个小工具在工作中也一直在用,正好领导说要写一篇这个工具的分析说明文章,经过询问可以发表博客。由于一直在用,所以末尾优劣势部分存在一些主观想法。 编写不易,如果能够帮助到你,希望能够点赞收藏加…

SpringCloud, SpringCloud-Alibaba,Nacos概述

目录 SpingCloud概述 1.SpringCloud是什么? 2.SpringCloud和SpringBoot的关系 3.SpringCloud-Alibaba概述 3.1.Netflix公司项目进入维护模式 3.2.Spring Cloud Alibaba是什么? 3.3.Spring Boot和Spring Cloud的版本号说明 3.Nacos总结 SpingCloud概述 1.Spri…

蓝桥杯刷题——基础篇(一)

这部分题目,主要面向有志参加ACM与蓝桥杯竞赛的同学而准备的,蓝桥杯与ACM考察内容甚至评测标准基本都一样,因此本训练计划提供完整的刷题顺序,循序渐进,提高代码量,巩固基础。因竞赛支持C语言、C、Java甚至…

【JAVA八股文】算法、数据结构、基础设计模式

算法、数据结构、基础设计模式1. 二分查找2. 冒泡排序3. 选择排序4. 插入排序5. 希尔排序6. 快速排序7. ArrayList8. Iterator9. LinkedList10. HashMap1)基本数据结构2)树化与退化3)索引计算4)put 与扩容5)并发问题6&…

从lettcue插件看skywalking

lettcue 的写操作是异步的。io.lettuce.core.RedisChannelWriter.write进行写入,io.lettuce.core.protocol.RedisCommand进行异步读取数据 skywalking 插件大体逻辑 在方法执行前,通过ContextManager创建span创建span的同时,判断trace上下文…

零信任-Akamai零信任介绍(6)

​Akamai零信任介绍 Akamai是一家专注于分布式网络服务的公司,它提供了一系列的互联网内容和应用加速服务。关于Akamai的零信任,它指的是Akamai的安全架构中不存在任何一个环节是可以被单独的控制或影响的,因此可以提供更高的安全性。通过使…

ChatGPT is not all you need,一文看尽SOTA生成式AI模型:6大公司9大类别21个模型全回顾(三)

文章目录ChatGPT is not all you need,一文看尽SOTA生成式AI模型:6大公司9大类别21个模型全回顾(三)Text-to-Text 模型ChatGPTLaMDAPEERMeta AI Speech from BrainText-to-Code 模型CodexAlphacodeText-to-Science 模型GalacticaM…

超简单!pytorch入门教程:Tensor

超简单!pytorch入门教程:Tensor 一、pytorch安装 安装pytorch之前,需要安装好python(废话),还没安装过python的宝宝请先移步到廖雪峰的python教程,待安装熟悉完之后,再过来这边。 …

C代码中访问链接脚本中的符号

一、目的在之前的《GNU LD脚本命令语言(一)》、《GNU LD脚本命令语言(二)》我们介绍了GNU链接脚本的知识点,基本上对链接脚本中的SECTION、REGION、以及加载地址与执行地址的关系等内容有了一定的了解。本篇主要讲解链…

工业4.0是如何优化垃圾处理行业的

如今,工业4.0正在影响着制造业和物流等行业,其发展潜力在未来还有望进一步扩大。一些全球领先的垃圾处理公司已经开始在水处理和废物回收等领域应用工业4.0。工业4.0的创新给这个领域带来了一些必要的改进。随着环境危机的加剧,垃圾处理行业面…

2022年最新数据库调查报告:超八成DBA月薪过万,你拖后腿了吗?

数据库管理员属于IT行业高薪职业的一种,近几年关于数据库管理员的薪资统计文章也层出不穷,那么当前,DBA们的薪资究竟到达了怎样的水平呢?墨天轮数据社区发布最新《2022年墨天轮数据库大调查报告》,数据显示超八成DBA月…

《MySQL学习》 全局锁和表锁

一.MySQL锁的分类 二.全局锁 全局锁对整个数据库加锁,可以执行如下命令,整个数据库都将处于只读状态。 Flush tables with read lock ;我们可以执行 unlock table进行解锁 unlock table ;读操作 非读操作(阻塞) 全局锁的典型使…

【并发编程】【2】进程与线程

并发编程 2.进程与线程 2.1 进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管…

开源大数据分析工具有几大内容?

在数据越来越重要的今天,数据管理的重要性不言而喻。引用专业的开源大数据分析工具可以为企业实现数字化办公,提升效率,提高数据管理品质和效率。我们今天就一起来了解下开源大数据分析工具的详细内容吧。 一、实现数据分析的重要性 在以前&a…

Java面试——Spring 事务

目录 1.什么是Spring 事务 2.Spring 事务的开启方式 3.Spring事务的实现方式/原理 4.事务传播机制 5.事务隔离级别 6.事务失效的原因 1.什么是Spring 事务 事务在逻辑上是一组操作,要么执行,要不都不执行。 如下: Begin; insert into…

【java】Spring Boot --spring boot项目整合xxl-job

文章目录1、源码下载地址2.文档地址3.源码结构4.初始化数据库脚本5.配置调度中心xxl-job-admin5.1 修改调度中心配置文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties5.2 启动调度中心5.3 访问调度中心管理界面6.创建执行器项目6.3 载入配置…

Framework——【MessageQueue】消息队列

定义 队列是 Apache RocketMQ 中消息存储和传输的实际容器,也是 Apache RocketMQ 消息的最小存储单元。 Apache RocketMQ 的所有主题都是由多个队列组成,以此实现队列数量的水平拆分和队列内部的流式存储。 队列的主要作用如下: 存储顺序性…