美文网首页Javascript收集
详解macrotasks和microtasks

详解macrotasks和microtasks

作者: 小进进不将就 | 来源:发表于2019-01-07 11:29 被阅读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


    (完)

    相关文章

      网友评论

        本文标题:详解macrotasks和microtasks

        本文链接:https://www.haomeiwen.com/subject/azjqrqtx.html