Javascript收集

详解macrotasks和microtasks

2019-01-07  本文已影响24人  小进进不将就

macrotasks(taskQueue):宏任务 task,也是我们常说的任务队列

macrotasks 的划分如下:(注意先后顺序!)
(1)setTimeout(延迟调用)
(2)setInterval(间歇调用)
(3)setImmediate(Node 的立即调用)
(4)requestAnimationFrame(高频的 RAF)
(5)I/O(I/O 操作)
(6)UI rendering(UI 渲染)

注意:
(1)每一个 macrotask 的回调函数要放在下一车的开头去执行!
(2)只有 setImmediate 能够确保在下一轮事件循环立即得到处理


例:以下代码输出什么?

setImmediate(()=>{
  console.log("aaa")
})

setImmediate(()=>{
  console.log("bbb")
  setImmediate(()=>{
    console.log("ddd")
  })
})

setImmediate(()=>{
  console.log("ccc")
})

结果:aaa bbb ccc ddd

解析:
(1)setImmediate 属于 macrotasks,有 3 个外层的 setImmediate ,所以理论上有 3 班车待执行
(2)第一班车(cycle 1)
先执行第一个 setImmediate ,它的回调函数放在下一车开头(cycle 2)执行

microtask 队列为空,执行下一车

(3)第二班车(cycle 2)
开头先执行第一个 setImmediate 的回调函数,
输出:aaa
再执行第二个 setImmediate ,它的回调函数放在下下一车开头(cycle 3)执行

microtask 队列为空,执行下一车

(3)第三班车(cycle 3)
开头先执行第二个 setImmediate 的回调函数,
输出:bbb
注意,该 setImmediate 的回调函数也有一个 macrotask ,它的回调函数放在下下下下一车(cycle 5)的开头执行,因为 cycle 4 是外层第三个 setImmediate,所以要往后稍稍
再执行第三个 setImmediate ,它的回调函数放在下下下一车(cycle 4)执行

microtask 队列为空,执行下一车

(4)第四班车(cycle 4)
开头先执行第三个 setImmediate 的回调函数,
输出:ccc

microtask 队列为空,执行下一车

(5)第五班车(cycle 5)
开头执行第二个 setImmediate 内部 setImmediate 的回调函数,
输出:ddd

此时 microtasks 已空,
同时整段代码执行完毕。


microtasks:微任务(也称 job)调度在当前脚本执行结束后,立即执行的任务,以避免付出额外一个 task 的费用。
例如响应事件、异步操作

microtasks 的划分如下:(注意先后顺序!)
(1)process.nextTick(Node 中 定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行)
(2)Promises(详情看这篇文章:https://www.jianshu.com/p/06d16ce41ed2
(3)Object.observe(原生观察者实现,已废弃)
(4)MutationObserver(监听 DOM change)
只有在 nextTick 空了才处理其它 microtask。(Next tick queue has even higher priority over the Other Micro tasks queue.)


例:

setImmediate(() => {
  console.log('immediate');
});
Promise.resolve(1).then(x => {
  console.log(x);
  return x + 1;
}).then(x => {
  console.log(x);
  return x + 1;
}).then(x => console.log(x));

结果:1 2 3 immediate

解析:
(1)第一车(cycle 1)
setImmediate 是 macrotasks 的第一个 task,它的回调函数放在 下一车(cycle 2)的开头执行
Promise.resolve 的三个 then() 是 microtask ,直接执行,直至结束
输出:1 2 3
(2)第二车(cycle 2)
先执行 setImmediate 的回调函数,
输出:immediate


一个事件循环(eventLoop)的执行顺序(非常重要):
① 开始执行脚本。
② 取 macrotasks(taskQueue)中的第一个 task 执行,该 task 的回调函数 放在下一个 task 开头 执行。
③ 取 microtasks 中的全部 microtask 依次执行,当这些 microtask 执行结束后,可继续添加 microtask 继续执行,直到 microtask 队列为空。
④ 取 macrotasks(taskQueue)中的第二个 task 执行,该 task 的回调函数 放在下一个 task 开头 执行。
⑤ 再取 microtasks 中的全部 microtask 依次执行,当这些 microtask 执行结束后,可继续添加 microtask 继续执行,直到 microtask 队列为空。
⑥ 循环 ② ③ 直到 macrotasks、microtasks 为空。


以下代码的运行结果是什么?(重点看例3
例1:

    console.log('a');

    setTimeout(() => {
      console.log('b');
    }, 0);

    console.log('c');

    Promise.resolve()
      .then(() => {
        console.log('d');
    })
      .then(() => {
        console.log('e');
      });

    console.log('f');

结果:acfdeb


例2:

    setTimeout(function(){
            console.log(1)
        },0);

    new Promise(function(resolve){
           console.log(2)

        for( var i=100000 ; i>0 ; i-- ){
            i==1 && resolve()
        }
           console.log(3)
        })
        .then(function(){
           console.log(4)
        });

           console.log(5);

结果:23541
注意:Promise 的 then() 是 microtask ,所以先输出 5,再执行 microtask 队列。


例3:

    console.log('start')

    const interval = setInterval(() => {
      console.log('setInterval')
    }, 0)

    setTimeout(() => {
      console.log('setTimeout 1')

      Promise.resolve()
        .then(() => {
          console.log('promise 1')
        })
        .then(() => {
          console.log('promise 2')
        })
        .then(() => {

          setTimeout(() => {
            console.log('setTimeout 2')
            Promise.resolve()
              .then(() => {
                console.log('promise 3')
              })
              .then(() => {
                console.log('promise 4')
              })
              .then(() => {
                clearInterval(interval)
              })
          }, 0)

        })

    }, 0)

    Promise.resolve()
      .then(() => {
        console.log('promise 5')
      })
      .then(() => {
        console.log('promise 6')
      })

结果:

    start
    promise 5
    promise 6
    setInterval
    setTimeout 1
    promise 1
    promise 2
    setInterval
    setTimeout 2
    promise 3
    promise 4

解析:
(1)先按照 macrotaskmicrotask 划分代码:

    console.log('start')

setInterval 是 macrotask,其回调函数在 microtask 后执行

    const interval = setInterval(() => {
      console.log('setInterval')
    }, 0)

setTimeout 是 macrotask,其回调函数放在下一车(cycle 2)执行

    setTimeout(() => ... , 0)

Promise.resolve() 的两个 then() 是 microtask

    Promise.resolve()
      //microtask
      .then(() => {
        console.log('promise 5')
      })
      //microtask
      .then(() => {
        console.log('promise 6')
      })

(2)第一班车(cycle 1):
进栈

    console.log('start')

第一个macrotask 是 setInterval,回调函数放下一车(cycle 2)的开头执行,
第二个macrotask 是 setTimeout,回调函数放下下一车(cycle 3)的开头执行,

清空栈,
输出:start
执行 microtasks,直至清空该队列,即 Promise.resolve() 的两个 then(),
输出:promise 5 promise 6

(3)第二班车(cycle 2):
执行 setInterval 的回调,
输出:setInterval
同时下一个 setInterval 也是 macrotask 但要放到 下下下一车(cycle 4)执行回调,即 下下一车(cycle 3)setTimeout 的后面

此时 setInterval 中没有 microtasks,所以该队列是空的,故进行下一车(cycle 3)

(4)第三班车(cycle 3
执行 setTimeout 的回调,
输出 setTimeout 1
执行 microtasks,直至清空该队列,即 Promise.resolve() 的第一个和第二个 then(),

输出:promise 1 promise 2

而 第三个 then() 中的 setTimeout 是 macrotask ,放到下下下下一车(cycle 5)执行回调,
第四个 then() 是紧跟着第三个 then() 的,所以在 下下下下一车(cycle 5)执行

此时 microtasks 已空,故进行下一车(cycle 4)

(5)第四班车(cycle 4
由(3)得,执行 setInterval ,
输出:setInterval

此时 setInterval 中没有 microtasks,所以该队列是空的,故进行下一车(cycle 5)

同时下一个 setInterval 也是 macrotask 但要放到 下下下下下一车(cycle 6)执行回调,

(6)第五班车('cycle 5')
由(4)得,执行 setTimeout
输出:setTimeout 2

执行 microtasks,直至清空该队列,即 Promise.resolve() 的第一个和第二个 then(),

输出:promise 3 promise 4

接着执行第三个 then() --> clearInterval(interval),将下下下下下一车(cycle 6)要执行回调的 setInterval 清除

此时 microtasks 已空,
同时整段代码执行完毕。


参考整理(看晕了):
Node.js 事件循环一: 浅析
https://github.com/ccforward/cc/issues/47
通过microtasks和macrotasks看JavaScript异步任务执行顺序https://www.jianshu.com/p/d3ee32538b53
macrotask与microtask(看10遍不过分)
http://www.ayqy.net/blog/javascript-macrotask-vs-microtask/#articleHeader1


(完)

上一篇下一篇

猜你喜欢

热点阅读