宏任务里面有微任务,那这个顺序应该怎么看呢

211 阅读4分钟

大家好,在最近的面试过程中,对于事件循环机制又有了一个新的理解,今天要说的这个点属于是我过去总结的一个扩展或者说一个更复杂的情况,这个情况就是,当我们的宏任务里面有微任务的情况(在之前我是没有考虑过的,也算是一个遗漏),如果大家在学习时也没有考虑到这一块,希望本章可以给大家带来一点点帮助。【本篇是以浏览器的js事件循环机制】

其次如果大家先重新复习一下 事件循环机制,也可以看看我另外一个文章。问:下面代码的宏任务微任务执行顺序是怎样的对于事件循环不知道有多少同学在面试的笔试题被它绕的团团转,作为js的处理事件的 - 掘金 (juejin.cn)

image.png

简单复习事件循环机制内容

事件循环机制是js中处理异步操作的核心机制,允许js处理多个任务而不会堵塞主线程。

  • 宏任务
    1. script(整体代码)
    2. setTimeout / setInterval
    3. Ajax请求的回调
    4. DOM 事件监听回调
    5. postMessage (H5, 向其它窗口分发异步消息)
    6. setImmediate(Node.js 环境)
  • 微任务
    1. Promise对应的then方法的回调
    2. async & await
      1. await语句之后为微任务
  • 执行代码的顺序:
    • 在执行栈中执行初始化同步代码
    • 执行过程中如果有启动异步任务, 交给对应的管理模块处理, 管理模块会在后面特定时间,将回调函数放入任务队列中待执行
    • 在执行栈中所有代码都执行完后, 依次取出任务队列中微任务,宏任务回调到执行栈中依次执行

简单的热身案例

console.log('script start');   // 同步

setTimeout(() => {             // 宏任务
    console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {//微任务
    console.log('Promise then');
});

console.log('script end');    //同步

根据代码后面给的提示,执行顺序是: script start => script end => promise then => setTimeout

本章主要讨论问题,宏任务内包含微任务

简单版

console.log("script start");             // 同步

setTimeout(() => {                       // 宏任务

console.log("setTimeout run");

Promise.resolve().then(() => {           //微任务
    console.log('Promise then');
});

console.log("setTimeout end");

}, 500);

console.log("script end");               // 同步

对于这段代码中,不知道大家学习这个知识的时候是什么情况,我那会就觉得微任务执行完就执行一个宏任务,所以对这种情况有点懵。

这个段代码的结果是: script start => script end => setTimeout run => setTimeout end => Promise then

解释:

  1. 同步代码执行 script start => 安排宏任务setTimeout => script end
  2. 延迟后,setTimeout 的宏任务被添加到宏任务队列
  3. 此时执行栈为空,检查微任务队列为空,开始查看宏任务队列
  4. 执行宏任务队列,打印console.log("setTimeout run")console.log("setTimeout end");微任务promise.then()加入微任务队列
  5. 宏任务执行完,检查微任务队列
  6. 执行微任务 promise.then()

自己理解:当这种嵌套情况出现的时候,把宏任务当作一个新的区域,把它当作一个新的开始。可以很好的理解这个代码

进阶一下

console.log("script start");                                // 同步(1)

const promiseA = new Promise((resolve, reject) => {         

console.log("init promiseA");                               // 同步(1)

resolve("promiseA");

});

const promiseB = new Promise((resolve, reject) => {

console.log("init promiseB");                              // 同步(1)

resolve("promiseB");

});

setTimeout(() => {                                         // 宏任务(1)

console.log("setTimeout run");                             // 宏任务里同步(2)

promiseB.then(res => {                                     // 宏任务里面的微任务(2)

console.log("promiseB res :>> ", res);                   

});

console.log("setTimeout end");                             // 宏任务里的同步(2)

}, 500);

promiseA.then(res => {                                    // 微任务(1)

console.log("promiseA res :>> ", res);

});

console.log("script end");                                // 同步(1)

对于上面这段代码,大家应该看到我对于代码中分析代码所标注的(number),为了让自己不分析乱,我将第一次放入执行栈、微任务和宏任务队列的代码先分大类的方式分好。

  1. 同步代码进入执行栈执行,script start => init promiseA => init promiseB => script end
  2. 在上面这个过程中, 微任务(1)进入了微任务队列,宏任务(1)进入了宏任务队列
  3. 执行栈空,检查微任务队列,发现微任务(1)
  4. resolve("promiseA");传了 promiseA 字符给了then 回调,所以输出 promiseA res :>>promiseA
  5. 此时微任务队列空,检查宏任务队列,发现有 宏任务(1)
  6. 执行宏任务,先执行了里面的两个输出的同步(2),其中将宏任务里面的微任务(2)加入了微任务队列。
  7. 宏任务执行完,检查微任务
  8. 执行微任务 promiseB res :>> promiseB

最终执行的顺序是:

image.png

总结

本章是对于浏览器js事件循环机制里面的微任务执行完,执行宏任务,再执行微任务,再执行宏任务,一直到执行完所有代码的情况。

在学习这段代码的时候,我觉得大家可以把宏任务看作是一个重新开始,宏任务自己为一个区域,重新分析同步,微任务,宏任务。这样会让思路变得清晰。