Execution order of macro tasks and micro tasks (super detailed explanation)

foreword

Before writing this, I also consulted a lot of articles, combined with my own understanding, talked about the understanding of the Event Loop model , and analyzed the impact of Promise and async/await in the task queue, and also gave a variety of situations Task cases and analysis and explanation , I believe everyone will gain something after reading it; of course, it is also my own understanding, and it is inevitable that there will be deviations. Welcome to correct me~

1. The basic concept of macro task and micro task

  1. The development of macro tasks and micro tasks and the reasons for their emergence:

JavaScript is a typical single thread (code is executed sequentially from top to bottom). However, when a task takes too long or multiple tasks need to be executed, it will inevitably lead to thread blocking and affect the rendering effect of the view.

In ES3 and previous versions, JavaScript itself has no asynchronous task capability. With the introduction of Promise, the JavaScript engine itself can also initiate asynchronous tasks. So far, JavaScript can be divided into synchronous tasks and asynchronous tasks; and JS further divides asynchronous tasks into macro tasks and micro tasks.

Due to the fast execution of micro-tasks, many tasks can be executed at one time . Clearing the micro-tasks immediately after the execution of the current macro-task can achieve the effect of pseudo-synchronization, which plays a vital role in the rendering effect of the view. The reason for the task.

  1. Basic types of macro tasks and micro tasks

Macro task:

  • script (outer synchronous code)

  • Ajax request

  • setTimeout、setInterval

  • postMessage、MessageChannel

  • setImmediate (Node.js environment), I/O (Node.js environment)

  • ...

micro task

  • Promise.then().catch() 和 .finally()

  • process.nextTick (Node.js environment)

  • ...

2. Event loop model (Event Loop)

As shown in the figure above, when the synchronization code is executed, all macro tasks will be executed. After the macro task is executed, it will be judged whether there is an executable micro task; if there is, the micro task will be executed. After completion, the macro task will be executed; If not, a new macro task is executed to form an event loop.

This is just an illustration of the execution relationship between macro tasks and micro tasks. So, how does js call methods in the Event Loop to process them?

  1. For the code we write, js will assign task types and put them into different task queues according to the type;

  1. Event Loop will push the functions that need to be executed into the event call stack for execution according to the execution timing;

  1. The same macro task will also have different execution timings (similar to the time of setTimeout), and the Event Loop will control the function that needs to be executed.

Summary: When Event Loop pushes events, it will judge whether there are events that need to be executed in the microtask queue: if there are, it will push the microtasks that need to be executed first; if not, it will push the macrotasks that need to be executed sequentially!

Remember, after the macro task is executed, it will be judged whether there are any micro tasks that need to be executed! ! ! In complex events, this point can often be wrong! ! !

Remember, after the macro task is executed, it will be judged whether there are any micro tasks that need to be executed! ! ! In complex events, this point can often be wrong! ! !

Remember, after the macro task is executed, it will be judged whether there are any micro tasks that need to be executed! ! ! In complex events, this point can often be wrong! ! !

3. Processing of Promise, async and await in the event loop

  1. Promise:

The process of creating an instance of new Promise is synchronous!

 console.log(1);
 new Promise((resolve, reject) => {
    console.log(2);
 })
 console.log(3);
// 结果: 1 2 3

However, the callback in Promise.then().catch().finally() is a microtask.

 console.log(1);
 new Promise((resolve, reject) => {
    console.log(2);
    resolve(); // 触发 then 回调
    // reject(); // 触发 catch 回调
 }).then(()=>{
    console.log('then')
 }).catch(()=>{
    console.log('catch')
}).finally(()=>{
    console.log('finally')
})
 console.log(3);
// 结果: 1 2 3 then finally

The picture above is the explanation of Promise in the rookie tutorial-JavaScript Promise . Let’s think about why we should design it like this:

我们假设Promise 不是立即执行的,会有什么后果?利用Promise,多是封装异步请求(Ajax),而请求不是立即请求的,还需要等待Promise 任务执行,那么我们就失去了网络请求的时效性,会导致页面等待渲染(因为我们上面提及的 Event Loop 会根据事件执行时机,选择将事件压入堆栈中,我们无法保证自己写的【不是立即执行Promise】什么时候执行)。

  1. async/await :

简单来说,async是通过Promise包装异步任务。

async function fun1() {
  console.log('fun1 start')
  await fun2(); // 等待 fun2 函数执行完成
  console.log('fun1 end')
}
async function fun2() {
  console.log('fun2 start')
  console.log('fun2 end')
}
fun1()

// 输出结果: fun1 start、fun2 start、fun2 end、fun1 end

遇到 await 则需要等待 await 后的代码执行完成后,在往下执行代码。因此,可以将 await 看作抢夺线程的标记,fun1 中,本来是同步执行的,但是 await 的出现,导致线程执行了 fun2 的代码后,再次回到await,往后执行。

同时,await后面的代码,会进入then微任务中!!!

同时,await后面的代码,会进入then微任务中!!!

同时,await后面的代码,会进入then微任务中!!!

async function fun1() {
  console.log('fun1 start')
  await fun2(); // 等待 fun2 函数执行完成
  console.log('我是 await 后面的代码')
  console.log('fun1 end')
}
async function fun2() {
  console.log('fun2 start')
  console.log('fun2 end')
}
fun1()
console.log('await 阻塞,导致 await后面代码进入 then 微任务')

这个知识点比较容易忽略,以为 await 回来后,直接继续执行后面的代码,这是不对的!

四、 process.nextTick在事件循环中的处理

process.nextTick是Node环境的变量,process.nextTick() 是一个特殊的异步API,其不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。所以,nextTick和Promise同时出现时,肯定是process.nextTick() 先执行

可以类比 Vue.$nextTick(),也是需要执行完这个函数后,才能继续Event Loop。

五、 典型案例

5.1 典型的宏任务、微任务

setTimeout(function () {
    console.log('1');
});

new Promise(function (resolve) {
    console.log('2');
    resolve();
})
  .then(function () {
      console.log('3');
  })
  .then(function () {
      console.log('4')
  });
        
console.log('5');

5.2 宏任务中包含微任务

setTimeout(function () {
    console.log('1');
    new Promise(function (resolve) {
        console.log('2');
        resolve();
    })
      .then(function () {
          console.log('3');
      })
    console.log('4');
});

console.log('5');

5.3 微任务中包含宏任务

console.log('1');
new Promise(function (resolve) {
    console.log('2');
    resolve();
})
  .then(function () {
      console.log('3');
      setTimeout(function () {
          console.log('4');
       })
      console.log('5');
  })

console.log('6');

5.4 async 宏任务

async function fun1() {
  console.log('fun1 start')
  setTimeout(function () {
      console.log('fun1 setTimeout');
  });
  await fun2();
  console.log('fun1 end')
}

async function fun2() {
  console.log('fun2 start')
  new Promise((resolve)=>{
      console.log('fun2 Promise')
      resolve()
  })
    .then(()=>{
        console.log('fun2 Promise then')
    })
  console.log('fun2 end')
}

fun1()

5.5 node 事件执行分析

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

5.6 综合案例

console.log('script start')

async function fun1() {
  console.log('fun1 start')
  process.nextTick(function() {
    console.log('fun1 process nextTick');
  })
  setTimeout(function () {
      console.log('fun1 setTimeout');
      new Promise(function (resolve) {
          console.log('fun1 Promise');
          resolve();
      })
        .then(function () {
            console.log('fun1 Promise then');
            setTimeout(function () {
                console.log('fun1 Promise then setTimeout');
             })
            console.log('fun1 Promise then end');
        })
  });
  await fun2();
  console.log('fun1 end')
}

async function fun2() {
  console.log('fun2 start')
  setTimeout(function () {
      console.log('fun2 setTimeout');
  });
  new Promise((resolve)=>{
      console.log('fun2 Promise')
      resolve()
  })
    .then(()=>{
        console.log('fun2 Promise then')
    })
  console.log('fun2 end')
}

fun1()

setTimeout(function() {
  console.log('setTimeout-000')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  process.nextTick(function() {
    console.log('Promise process nextTick');
  })
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
    process.nextTick(function() {
      console.log('promise2 process nextTick');
    })
  })

console.log('script end')

六、案例分析

6.1 典型的宏任务、微任务

 // 同步任务
 console.log('2'); // new promise 实例化过程
 console.log('5');
 // 
 // 将 setTimeout console.log('1'); 放入宏任务队列
 // 将Promise 的回调放入微任务队列
 // then  console.log('3');
 // then  console.log('4')

 // 先微任务
 console.log('3');
 console.log('4')
// 再宏任务
 console.log('1');

// 因此,输出结果: 2 5 3 4 1

6.2 宏任务中包含微任务

// 同步任务
console.log('5');
// 将 setTimeout 放入宏任务队列,此时,没有微任务执行,因此,开始执行setTImeout宏任务
console.log('1');
// new Promise 实例化 同步执行:
console.log('2');
// 将Promise.then 回调放入微任务
// 当前(setTimeout)的宏任务事件还没有执行完!!!
// 注意哈!!当前(setTimeout)的宏任务事件还没有执行完!!!,事件未跳出当前 Loop
console.log('4');
// 执行完宏任务,开始执行 微任务
console.log('3');

// 因此,结果为: 5 1 2 4 3

6.3 微任务中包含宏任务

 // 同步代码:
console.log('1');
// new Promise 
console.log('2');
console.log('6');
// 微任务:Promise.then
console.log('3');
console.log('5');
// 结束当前 Loop[有上个例子,这个就不难理解了]
// 开启宏任务
console.log('4');

// 因此,结果为:1 2 6 3 5 4

6.4 async 宏任务

 // fun1
console.log('fun1 start')
// 将setTimeout 放入宏任务队列
//  await fun2(); 进入 fun2
console.log('fun2 start')
//   new Promise
console.log('fun2 Promise')
// 将 then 放入微任务
console.log('fun2 end') // 当前任务队列

// 有微任务,先执行微任务
console.log('fun2 Promise then')
// 回到 await 处
console.log('fun1 end') // 当前 fun1 队列
console.log('fun1 setTimeout'); // 最后的宏任务

6.5 node 事件执行分析

// 从上往下:
console.log('1'); // 同步代码
//  setTimeout 宏任务1
//  process.nextTick 微任务1
console.log('7'); // new Promise
// Promise.then 微任务2
// setTimeout 宏任务2
// -- 开始执行微任务
console.log('6');
console.log('8')

// -- 开始宏任务1 
console.log('2');
//  process.nextTick 微任务!!!
console.log('4'); // new Promise
// Promise.then 微任务!!!
// 到此,当前宏任务已执行完毕,有微任务,需要先执行微任务
console.log('3');
console.log('5')
        
// 执行宏任务 2
console.log('9');
//  process.nextTick 微任务
console.log('11');// new Promise
// Promise.then 微任务!!!
console.log('10');
console.log('12')

// 因此,结果为:1 7 6 8 2 4 3 5 9 11 10 12

6.6 综合案例

// 这个案例,就应用了 await 导致的 then 微任务细节,我第一次分析也错了
// 还涉及了process.nextTick node 的执行时机优先
         
// 开始分析:
        console.log('script start')
        // fun1() 进入 fun1
        console.log('fun1 start')
        // 生成微任务 process.nextTick(fun1)
        // 生成 宏任务 setTimeout (fun1)
        await fun2(); // 进入 fun2
        console.log('fun2 start')
        // 生成宏任务 setTimeout (fun2)
        console.log('fun2 Promise') // new Promise
        // 生成 Promise.then 微任务
        console.log('fun2 end')
        // !!!此时,fun2 已经有返回值了,不需要等待 fun2 中的事件执行,回到 await 处,被标记了 await .then 的微任务
        // 因此,执行主任务
        console.log('Promise') // new Promise
        // 生成 Promise process.nextTick 微任务
        // 生成 Promise1.then 微任务
        // 生成 Promise2.then 微任务
        console.log('script end')
        /**
         * 分析微任务队列
         * 1. 第一个微任务:process.nextTick(fun1)
         * 2. fun2 Promise.then // 容易漏
         * 3. await .then 的微任务 !!!!!!!!!!!!!
         * 4. Promise process.nextTick 微任务
         * 5. Promise1.then 微任务
         * 6. Promise2.then 微任务
         *
        */
        // 根据 Node process 优先级,先执行 process
        console.log('fun1 process nextTick');
        console.log('Promise process nextTick');
        console.log('fun2 Promise then')
        // await.then微任务[await 后的所有代码,如果还有任务,具体再分析即可]
        console.log('fun1 end')
        console.log('promise1')
        console.log('promise2') // 执行到这,又生成新的 process.nextTick 微任务,又先执行
        console.log('promise2 process nextTick');
        // 没有微任务了,开始执行宏任务
        console.log('fun1 setTimeout');
        console.log('fun1 Promise'); // 生成新的 promise.then 微任务,当前宏任务已执行完成,开始执行微任务
        console.log('fun1 Promise then'); // 生成新的 宏任务 fun1 Promise then setTimeout
        console.log('fun1 Promise then end');
        /**
         * 此时,分析宏任务队列
         * 1.  第一个 是 fun2 setTimeout
         * 2.  setTimeout(function () { console.log('setTimeout-000') }, 0)
         * 3.  fun1 Promise then setTimeout
         * */
        // 因此, 依次执行宏任务
        console.log('fun2 setTimeout');
        console.log('setTimeout-000')
        console.log('fun1 Promise then setTimeout');

这个案例比较复杂,某些事件容易漏掉,因此,建议大家手动勾起来,每一个事件都对应到事件队列中,这个案例,考察两个点,一个是 await的处理及 node.process 的优先级。大家弄懂这个案例,出去面试,手撕这种题目应该不是问题了。

好啦,以上就是对宏任务、微任务、事件循环、案例的整体整理,有不对的地方,欢迎指正呀!

Guess you like

Origin blog.csdn.net/weixin_47746452/article/details/129244590