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

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

js 复制代码
await new Promise(resolve => {
        setTimeout(() => {
          resolve();
        }, 0);
      });

前置条件

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

案例

案例一

js 复制代码
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后边函数的结果之后才会放行,因此我们可以理解为这段代码目前是绑定在一起的

js 复制代码
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会开始执行,执行完后就不会阻塞后边的代码了,此时我们再继续把代码放入任务队列中

首先遇到了微任务 ,因此放入微任务队列

js 复制代码
 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之后的微任务啦。 这个讨论大家可以参考我文末的参考文献,这里不多赘述。

案例二

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

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

js 复制代码
//注:在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) juejin.cn/post/684490...

「前端面试题系列1」今日头条 面试题和思路解析 - 掘金 (juejin.cn) juejin.cn/post/684490...

javascript - 8张图帮你一步步看清 async/await 和 promise 的执行顺序 - 前端进阶 - SegmentFault 思否 segmentfault.com/a/119000001... ------------------------------------------------ 版权声明:本文为CSDN博主「想干到35岁的程序猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/weixin_4897...

相关推荐
前端进阶者7 分钟前
天地图Marker跳一跳动画
前端
火柴就是我10 分钟前
每日见闻之Three.js 根据官方demo 理解相机位置
前端
JosieBook20 分钟前
【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程
前端·spring boot·交互
刘大猫.27 分钟前
npm ERR! cb() never called!
前端·npm·node.js·npm install·npmm err·never called
咔咔一顿操作31 分钟前
常见问题三
前端·javascript·vue.js·前端框架
上单带刀不带妹32 分钟前
Web Worker:解锁浏览器多线程,提升前端性能与体验
前端·js·web worke
电商API大数据接口开发Cris1 小时前
Node.js + TypeScript 开发健壮的淘宝商品 API SDK
前端·数据挖掘·api
还要啥名字1 小时前
基于elpis下 DSL有感
前端
一只毛驴1 小时前
谈谈浏览器的DOM事件-从0级到2级
前端·面试
用户8168694747251 小时前
封装ajax
前端