• macro-task和micro-task 简介


    先看下面的代码

    <script>
            console.log(1);
            setTimeout(function(){
                console.log(2);
            },0);
            console.log(3);
        </script>
    

    上面的执行结果是1,3,2
    原因:上面的setTimeout可以理解为异步函数调用,因为javascript是单线程的,主线程拥有一个执行栈和一个事件循环
    当代码开始执行的时候,主线程会依次执行代码(就是script里面的代码),当遇到异步函数的时候(setTimeout),会将该函数加入到任务队列里面,然后继续执行,当主线程空闲后,然后把异步函数出栈,直到所有的异步函数执行完毕即可。

    在一个浏览器环境中,只能有一个事件循环,但是可以有多个任务队列,而每个任务都有一个任务源,相同任务源的任务,只能放到一个任务队列中

    上面的micro-task和macro-task就是两种不同的任务队列

    macro-task:script(script标签里面的整体代码) setTimeout,setInterval,setImmediate,I/O,UI rendering
    micro-task:process.nextTick,Promise,MutationObserver

    具体流程
    首先:全部代码(script)算一个macrotask.。
    第一步:浏览器先执行一个macrotask;执行的过程中,创造了新的macrotask(setTimeout之类的),然后接着执行,把promise加入到micro-task队列里面
    第二步:浏览器执行microtask(例如promise),这里会将microtask里面所有任务都取出
    第三步:重复,浏览器会再执行一个macrotask
    总的来说:macrotask每次只取一个,而microtask会一次取完

    下面再来另外一个例子:

    <script>
            console.log(1);
            setTimeout(function(){
                console.log(2);
            },0)
            Promise.resolve().then(function(){
                console.log(3);
            }).then(function(){
                console.log(4);
            })
            console.log(5)
                   
        </script>
    

    上面的输出结果是 1,5,3,4,2

    具体流程大概是下面这样:

    当代码开始执行的时候,会先输出1,然后把setTimeout加入到一个macrotask 队列里面,接着把promise加入到microtask 队列里面,然后输出5
    到这里我们代码执行完了一个macrotask(script里面的代码算第一个macrotask),接着要开始执行microtask,这次会把microtask里面所有的任务都执行完,这里就输出3和4,
    当microtask执行完后,又会接着开始执行macrotask,就是setTimeout,到这里输出完2后,所有代码就都执行完毕

    有个疑问 啊嘞嘞?是什么原因导致了原本应该在setTimeout回调后面的Promise的回调反而跑到前面去执行了呢?

    为了搞清这个问题,我专门去翻阅了一下资料,首先找到了Promises/A+标准里面提到:

    • 一个事件循环有一个或者多个任务队列;
    • 每个事件循环都有一个microtask队列
    • macrotask队列就是我们常说的任务队列,microtask队列不是任务队列
    • 一个任务可以被放入到macrotask队列,也可以放入microtask队列
    • 当一个任务被放入microtask或者macrotask队列后,准备工作就已经结束,这时候可以开始执行任务了。

    可见,setTimeout和Promises不是同一类的任务,处理方式应该会有区别,具体的处理方式有什么不同呢?我从这篇文章里找到了下面这段话:

    通俗的解释一下,microtasks的作用是用来调度应在当前执行的脚本执行结束后立即执行的任务。 例如响应事件、或者异步操作,以避免付出额外的一个task的费用。
    microtask会在两种情况下执行:

    1.任务队列(macrotask = task queue)回调后执行,前提条件是当前没有其他执行中的代码。
    2.每个task末尾执行。
    另外在处理microtask期间,如果有新添加的microtasks,也会被添加到队列的末尾并执行。

    也就是说执行顺序是:

    开始 -> 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 -> 再次取出microtask全部任务执行 -> ... 这样循环往复

    Promise一旦状态置为完成态,便为其回调(.then内的函数)安排一个microtask。

    接下来我们看回我们上面的代码

     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);
    

    按照上面的规则重新分析一遍:

    1.当运行到setTimeout时,会把setTimeout的回调函数console.log(1)放到任务队列里去,然后继续向下执行。
    2.接下来会遇到一个Promise。首先执行打印console.log(2),然后执行for循环,即时for循环要累加到10万,也是在执行栈里面,等待for循环执行完毕以后,将Promise的状态从fulfilled切换到resolve,随后把要执行的回调函数,也就是then里面的console.log(4)推到microtask里面去。接下来马上执行马上console.log(3)。
    3.然后出Promise,还剩一个同步的console.log(5),直接打印。这样第一轮下来,已经依次打印了2,3,5。
    4.现在第一轮任务队列已经执行完毕,没有正在执行的代码。符合上面讲的microtask执行条件,因此会将microtask中的任务优先执行,因此执行console.log(4)
    5.最后还剩macrotask里的setTimeout放入的函数console.log(1)最后执行。

    如此分析输出顺序是:

        2
        3
        5
        4
        1
    

    看吧,这次分析对了呢ヾ(◍°∇°◍)ノ゙

    总结和参考资料

    microtask和macrotask看起来容易混淆,实际上还是很好区分的。macrotask就是我们常说的任务队列(task queue)。

    JavaScript执行顺序可以简要总结如下:

    开始 -> 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 -> 再次取出microtask全部任务执行 -> ...

    循环往复,直至两个队列全部任务执行完毕。

    Tasks, microtasks, queues and schedules
    github-setImmediate.js
    知乎-Promise的队列与setTimeout的队列有何关联?
    阮一峰-JavaScript 运行机制详解:再谈Event Loop
    百度

  • 相关阅读:
    用idea开发springboot项目,提示Cannot resolve symbol 'RequestMapping'
    idea中创建maven项目是项目结构缺失,只有pom.xml文件
    Mysql5.7.17解压版安装
    scp命令,Linux系统之间通过目录或文件
    有关Linux中文件权限笔记
    主机映射
    在Linux中配置系统环境变量
    找回因克隆丢失的eth0网卡
    Linux系统中安装jdk
    Linux常用命令(vi/vim文本编辑)
  • 原文地址:https://www.cnblogs.com/liliuyu/p/12690144.html
Copyright © 2020-2023  润新知