一文让你搞懂async/await存在时的执行顺序

news2024/11/26 8:34:36

最近在写项目,发现代码中总出现这样的内容,一直百思不得其解,后来同事说这段代码的意义是把后边的任务变成宏任务执行,这我更迷惑了,我发现自己的Promise基础还是很差,因此在摸索了几天后,感觉好像摸索到了一些原理,特此记录,我觉得我也许很多想法都是错误的,请大佬们批评指正。

  // ......
      await new Promise(resolve => {
        setTimeout(() => {
          resolve();
        }, 0);
      });
 // ......

1. 前置条件

首先你要弄明白JS事件循环(Event Loop)的基本原理,要明白任务队列(即宏任务)、微任务队列、执行栈(同步任务)的执行顺序,在此不做过多阐述。

2. 案例

2.1 案例一

const fn = async () => {
  setTimeout(() => {
    console.log('after 0s');
  }, 0);
  new Promise((resolve) => {
    resolve('我是微任务啦');
  }).then((res) => {
    console.log(res);
  });
  console.log('我是同步任务哎');
  
  await new Promise((resolve) => {
     resolve("我成功喽")
     });
  new Promise((resolve) => {
    resolve('我是await之后的微任务啦');
  }).then((res) => {
    console.log(res);
  });
  console.log('我是await之后的同步任务啦');
};
fn();

这段代码的执行顺序是:

原因如下,我们用事件循环机制来一步步分析

1、首先,代码从上运行,console.log('after 0s')是宏任务,而且执行时间是0s,因此把回调函数放入到宏任务队列

2、遇到resolve('我是微任务啦')之后,发现是一个Promise.then,放入到微任务队列

3、紧接着遇到console.log('我是同步任务哎'),这是同步任务,直接放入执行栈即可

到目前为止,事件循环队列中的任务如下:

4、最关键的来喽,这里遇到了await,我们知道await会阻塞后边的代码,要等获取到await后边函数的结果之后才会放行,因此我们可以理解为这段代码目前是绑定在一起的

await new Promise((resolve) => {
    resolve('我成功喽');
  });
  new Promise((resolve) => {
    resolve('我是await之后的微任务啦');
  }).then((res) => {
    console.log(res);
  });
  console.log('我是await之后的同步任务啦');

而await是then的语法糖,因此  await new Promise((resolve) => {    resolve('我成功喽');  })这段代码其实是一个微任务!!!只不过由于await会暂时阻塞后边的代码而已!!!

而且一旦遇到await阻塞,JS就会把控制权交给我们的全局作用域,因此会从执行栈中的同步任务开始逐个进行执行!!!

因此目前队列中如下:

大家不要着急,我们开始一步步分析代码的执行:

1)首先肯定执行同步任务,于是打印  我是同步任务哎

2)同步任务其实也属于宏任务,因此会马上执行微任务,并需要把微任务队列清空,因此会打印  我是微任务啦,清空以上队列后,此时事件循环中的任务如下:

3)此时会继续第二个微任务,也就是await new Promise(()=》{resolve('我成功喽')}),

请注意,由于new Promise本身是一个同步事件,里面只有一行代码resolve('我成功喽'),因此该Promise的状态会立马变成 resolved,那么await会开始执行,执行完后就不会阻塞后边的代码了,此时我们再继续把代码放入任务队列中

  •  首先遇到了微任务 ,因此放入微任务队列
 new Promise((resolve) => {
    resolve('我是await之后的微任务啦');
  }).then((res) => {
    console.log(res);
  });
  • 然后遇到了console.log('我是await之后的同步任务啦'),放入同步任务中

因此现在的事件循环内容如下:


 

那么到这里,是不是你已经知道如何运行下面的代码啦。

当执行完await后,由于await之后的代码不再阻塞,所以会执行同步任务  我是await之后的同步任务啦,然后执行微任务  我是await之后的微任务啦,最后会执行宏任务打印出  after 0s

至此,所有代码执行完毕。

 注意:这里在以前的讨论中,不同的浏览器会有不同的结果,有的打印顺序是:我是await之后的微任务啦==>  console.log('我是await之后的同步任务啦');但是有的是console.log('我是await之后的同步任务啦')==>我是await之后的微任务啦

这个讨论大家可以参考我文末的参考文献,这里不多赘述。

2.2 案例二 

当我们讨论完案例一,大家对await的原理我想已经了解的差不多了,那么言归正传,回到文章开头提到的问题,为什么要用这段代码。

这里我举一个比较简单的例子,来快速说清楚,因为案例一已经足够繁琐了。

//注:在vue中,页面是在执行所有的同步代码执行完之后才能得到渲染。
const fn = async () => {
  setTimeout(() => {
    console.log('这是一个异步操作,模拟页面渲染');
  }, 0);
  
  console.log('我是同步任务哎');
  await new Promise((resolve) => {
    setTimeout(() => {
      resolve('我成功喽');
    }, 0);
  });
  console.log('我是await之后的同步任务啦');
};
fn();

首先公布答案:

我们看到 我是await之后的同步任务啦 是最后才执行,这样可以让页面得到渲染之后再执行await之后的代码,那么这个场景会应用到哪里呢?

注:在vue中,页面是在执行完所有的同步代码才去渲染页面,假设该案例中没有await new Promise这段代码,那么页面肯定是从上到下执行完  console.log('我是await之后的同步任务啦')才进行渲染页面,但是如果我想让页面渲染之后,再执行  console.log('我是await之后的同步任务啦')代码,该怎么办呢?

解决办法就是如下图片所述:我用await new Promise,里面包着定时器,定时器是宏任务,即异步任务,且会阻塞下面的代码,因此当代码执行到await后,就会认为同步代码已经执行结束了,因此会清除异步任务里的代码,而异步任务里也包含了new Promise中的定时器,当定时器执行完毕,Promise的状态就发生改变了,因此就不再阻塞下面的代码,就会继续执行了

废话少说,我们开始执行代码

1)首先,代码遇到定时器,会放入宏任务队列

2)然后遇到同步任务 console.log('我是同步任务哎'),会放入到执行栈中

3)然后遇到await,好!!!就此打住!!!因为案例一中我们说了,当遇到await,会放入到微任务队列,且会阻塞代码,因此下面的代码我们先不看,目前的事件循环事件如下:

那么肯定先打印  我是同步任务哎 ,其次进入到微任务,这里我们发现一个大问题,那就是,Promise中是一个定时器,而定时器是宏任务,因此会把定时器放入到宏任务中,即如下:

那此时微任务里的await被阻塞了,微任务就没有任务了,因此会去宏任务执行打印 这是一个异步操作,模拟页面渲染 ,执行完之后会继续执行 resolve('我成功喽'),一旦Promise状态发生改变,await就会执行,即此时会跳到微任务队列执行await,因此代码也不会阻塞,此时会把  console.log('我是await之后的同步任务啦') 拿到执行栈中,即:

最后会执行打印 我是await之后的同步任务啦

至此,代码结束。

总结 

1)当遇到await,会先暂停await及后边代码的执行,直到Promise的状态发生改变后,才会继续执行await以及后边的任务

2)await本质是then的语法糖,其实是个微任务

3)在await new Promise中如果包含一个定时器,定时器的回调函数中写resolve()或者reject(),那么这个定时器是个宏任务,会在宏任务队列排队完成后,再改变Promise的状态,然后await才能执行,再取消阻塞

注:此文参考文献来自于以下文章,非常好的帖子,大家可以去欣赏学习。

令人费解的 async/await 执行顺序 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/6844903762478235656#comment

「前端面试题系列1」今日头条 面试题和思路解析 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/6844903759957458952?utm_source=gold_browser_extension

javascript - 8张图帮你一步步看清 async/await 和 promise 的执行顺序 - 前端进阶 - SegmentFault 思否icon-default.png?t=N7T8https://segmentfault.com/a/1190000017224799#

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

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

相关文章

SAP ABAP内部单位外部单位转换问题

之前没有留意,用户用了那么久也没有发现,今天用户找过来说他在前台维护的是PC,但是报表里面显示是ST。 看了一下原来这里的“内部度量单位”就是内部的、与语言无关的计量单位格式(ST)。该内部格式在输出到屏幕之前&a…

完整指南:如何使用 Node.js 复制文件

文件拷贝指的是将一个文件的数据复制到另一个文件中,使目标文件与源文件内容一致。Node.js 提供了文件系统模块 fs,通过该模块可以访问文件系统,实现文件操作,包括拷贝文件。 Node.js 中文件拷贝方法 在 Node.js 中,有…

竞赛选题 大数据商城人流数据分析与可视化 - python 大数据分析

0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于大数据的基站数据分析与可视化 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🥇学长这里给一个题目综合评分(每项满分5分) 难度…

Postgresql源码(114)视图权限授予逻辑

0 速查 被授权的对象在系统表中记录授权信息,例如pg_namespace中的nspacl列: {mingjieUC/mingjie,UC/mingjie,pusr1UC/mingjie}pusr1UC/mingjie的含义: mingjie是赋予者pusr1是被赋予者UC是权限,表示USAGE和CREATE 1 视图权限…

@SpringBootApplication注解的理解——如何排除自动装配 分布式情况下如何自动加载 nacos是怎么被发现的

前言 spring作为主流的 Java Web 开发的开源框架,是Java 世界最为成功的框架,持续不断深入认识spring框架是Java程序员不变的追求。 本篇博客介绍SpringBootApplicant注解的自动加载相关内容 其他相关的Spring博客文章列表如下: Spring基…

ubuntu20.04 jammy 安装ros2

ubunut22.04 jammy(5.15) ros2版本: humble 安装参考: Ubuntu (Debian packages) — ROS 2 Documentation: Humble documentationl 按照官方给的操作指南进行操作即可,到安装软件包的时候,若只为开发&#xff0…

C++中实现雪花算法来在秒级以及毫秒及时间内生成唯一id

1、雪花算法原理 雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法,通常用于分布式系统中,以确保生成的ID在整个分布式系统中具有唯一性。它的名称来源于雪花的形状,因为生成的ID通常是64位的整数&#xff0…

【小白专属02】SpringBoot集成MybatisPlus

目录 前言 一、添加依赖项 二、配置数据库连接信息 三、数据库创建表 四、创建实体表 五、编写Mapper接口 六、功能测试 前言 上节回顾 上一节我们对SpringBoot框架进行了搭建,其实按照流程走问题不大。最主要的部分我们把下载SpringBoot demo的网址换成了…

编程每日一练(多语言实现)基础篇:百元买百鸡

文章目录 一、实例描述二、技术要点三、代码实现3.1 C 语言实现3.2 Python 语言实现3.3 Java 语言实现3.4 JavaScript 语言实现 一、实例描述 中国古代数学家张丘建在他的 《算经》 中提出了一个著名的 "百钱买百鸡问题", 鸡翁一,值钱五&…

重新定义旅行,AnyGo for Mac让你畅享虚拟GPS位置的无限可能!

旅行是一种令人兴奋和激动人心的体验,而现在有了AnyGo for Mac这款强大的虚拟GPS位置工具,你将能够重新定义旅行,尽情探索全新的地理环境和体验! AnyGo for Mac为你带来了全新的旅行方式。无需离开家门,你就可以通过模…

基于php的物流信息公共平台设计与实现

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于php的物流信息公共平…

面对海量数据挑战,企业怎样进行数据处理?

海量数据给企业带来机遇的同时,也给企业带来了一定的挑战。怎样高效处理海量数据也是成为了企业面临的重要问题。 一:数据采集 数据采集是数据处理的第一步。企业需要进行数据采集,但是要保证数据的准确性以及安全性。同时需要注意&#xf…

PgSQL-向量数据库插件-lantern

PgSQL-向量数据库插件-lantern 即pgvector、pg_embedding 后又一向量数据库扩展Lantern问世了。当然也为向量列提供了hnsw索引以加速ORDER BY... LIMIT查询。Lantern使用usearch实现hnsw。 使用方法 保留了标准PgSQL接口,兼容其生态工具。首先需要安装该插件&#x…

服务器基础知识:raid卡有什么优势?

当今直播电商、短视频呈现热火朝天的趋势,是近两年的商业热门议题,以淘宝天猫、拼多多、抖音、小红书、视频号为代表的平台数据量呈现爆炸性的增长。据IDC预测,2025年全球数据总量将增至175ZB(1ZB1万亿GB),…

数字电视码流分析仪通用规范

声明 本文是学习GB-T 26274-2010 数字电视码流分析仪通用规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了数字电视码流分析仪(以下简称码流分析仪)的技术要求、试验方法、检验规则及标 志、包装、运输、贮存等。 本标准适…

C++代码示例:组合数简单生成工具

文章目录 前言代码仓库内容代码(有详细注释)编译和运行命令结果总结参考资料作者的话 前言 C代码示例:组合数简单生成工具。 代码仓库 yezhening/Programming-examples: 编程实例 (github.com)Programming-examples: 编程实例 (gitee.com) …

echarts 折线组件

echarts 折线组件 <template><div class"lineChartsTemplate" :id"chartsId"></div> </template><script> export default {name: "lineChartsTemplate",components: {},props: {xData: {type: Array,default: ()…

个人在运行python代码过程中的坑230928

0关于GPT和百度使用的区别 就我个人使用经验而言&#xff0c;我觉得百度、知乎、csdn更是直接奔着解决问题去的&#xff0c;直接方便快捷&#xff0c;不需要你再对信息进行加工&#xff0c;可以直接找到经验。 GPT则是需要我们给他一个输入&#xff0c;给他一个提示&#xff0…

提高网申通过率的秘籍,校园招聘之春招秋招都有效

提高网申通过率的秘籍&#xff01;校园招聘&#xff08;校招-春招-秋招&#xff09;都有效。 网申&#xff0c;指的是在网上申请招聘企业的岗位&#xff0c;每年春招秋招&#xff0c;都是毕业生们的重点大事&#xff0c;如何把握好机会&#xff0c;就在于各个环节的细节之处&…

联盟 | 彩漩 X HelpLook,AI技术赋能企业效率提升

近日&#xff0c;AI 驱动的 PPT 协作分享平台「 彩漩 」与 AI 知识库搭建工具「 HelpLook」&#xff0c;携手为用户工作流注入更多智能和创造力&#xff0c;全面拥抱 AIGC 时代带来的机遇&#xff0c;致力于提供前沿的智能解决方案。 彩 漩 彩漩是一个以 AI 技术为基础、贯彻 …