zoukankan      html  css  js  c++  java
  • 异步 JavaScript 之 macrotask、microtask

    1、异步任务运行机制

    先运行下面的一段代码:

    console.log('script start');
    setTimeout(function() {
     console.log('setTimeout');
    }, 0);
    
    console.log('script end');
    
    //"script start"
    //"script end"
    //"setTimeout"
    

    这里一看,setTimeout的延时为 0 ,那么是不是程序执行到这里之后就立即执行setTimeout里面的函数呢?其实不是的.
    这是因为 JavaScript 主线程拥有一个 执行栈 以及一个 任务队列,主线程会依次执行代码,当遇到函数时,会先将函数 入栈,函数运行完毕后再将该函数 栈,直到所有代码执行完毕。

    那么遇到 WebAPI(例如:setTimeout, AJAX)这些函数时,这些函数会立即返回一个值,从而让主线程不会在此处阻塞。而真正的异步操作会由浏览器执行,浏览器会在这些任务完成后,将事先定义的回调函数推入主线程的 任务队列 中。

    而主线程则会在 清空当前执行栈后,按照先入先出的顺序读取任务队列里面的任务。

    那么我们来看一下上面程序的执行顺序:

    // 1. 开始执行
    
    console.log('script start'); // 2. 打印字符串 "script start"
    setTimeout(
     function() { // 5. 浏览器在 0ms 之后将该函数推入任务队列
     // 而到第5步时才会被主线程执行
     console.log('setTimeout'); // 6. 打印字符串 "setTimeout"
     },
     0
    ); // 3. 调用 setTimeout 函数,并定义其完成后执行的回调函数
    
    console.log('script end'); // 4. 打印字符串 "script end"
    // 5. 主线程执行栈清空,开始读取 任务队列 中的任务
    

    以上就是浏览器的异步任务的执行机制,核心点为:

    • 异步任务是由浏览器执行的,不管是AJAX请求,还是setTimeout等 API,浏览器内核会在其它线程中执行这些操作,当操作完成后,将操作结果以及事先定义的回调函数放入 JavaScript 主线程的任务队列中
    • JavaScript 主线程会在执行栈清空后,读取任务队列,读取到任务队列中的函数后,将该函数入栈,一直运行直到执行栈清空,再次去读取任务队列,不断循环
    • 当主线程阻塞时,任务队列仍然是能够被推入任务的。这也就是为什么当页面的 JavaScript 进程阻塞时,我们触发的点击等事件,会在进程恢复后依次执行。

    2、Macrotasks 和 Microtasks

    Macrotask 和 microtask 都是属于上述的异步任务中的一种,我们先看一下他们分别是哪些 API :

    • macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering

    • microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver

    setTimeout 的 macrotask ,和 Promise 的 microtask 有什么不同呢? 我们通过下面的代码来展现他们的不同点:

    
    console.log('script start');
    setTimeout(function() {
     console.log('setTimeout');
    }, 0);
    
    Promise.resolve().then(function() {
     console.log('promise1');
    }).then(function() {
     console.log('promise2');
    });
    
    console.log('script end');
    //"script start"
    //"script end"
    //"promise1"
    //"promise2"
    //"setTimeout"
    

    在这里,setTimeout的延时为0,而Promise.resolve()也是返回一个被resolve了promise对象,即这里的then方法中的函数也是相当于异步的立即执行任务

    这里的运行结果是Promise的立即返回的异步任务会优先于setTimeout延时为0的任务执行。

    原因是任务队列分为 macrotasks 和 microtasks,而Promise中的then方法的函数会被推入 microtasks 队列,而setTimeout的任务会被推入 macrotasks 队列。在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空。

    注:一般情况下,macrotask queues 我们会直接称为 task queues,只有 microtask queues 才会特别指明。

    那么也就是说如果我的某个 microtask 任务又推入了一个任务进入 microtasks 队列,那么在主线程完成该任务之后,仍然会继续运行 microtasks 任务直到任务队列耗尽。

    而事件循环每次只会入栈一个 macrotask ,主线程执行完该任务后又会先检查 microtasks 队列并完成里面的所有任务后再执行 macrotask

    参考:清蒸不是水煮 的文章
    阮一峰 - JavaScript 运行机制详解:再谈Event Loop
    Philip Roberts - Help,I’m stuck in an event loop

  • 相关阅读:
    hystrix熔断器之HystrixRequestLog
    RXJAVA之聚合操作
    RXJAVA之变换操作
    RXJAVA之创建被观察者
    小程序学习
    redis的使用
    Stream/Bytes[]/Image对象相互转化
    消息队列转载
    业务层使用事务
    ViewState 和字段属性的差异
  • 原文地址:https://www.cnblogs.com/feiyu6/p/8522322.html
Copyright © 2011-2022 走看看