你笑了

你的笑,是星星跳跃浪花的笑

0%

nextTick vs microtask

nextTick queue

  • 由 node 本身维护
  • 通过 process.nextTick() 添加任务
  • 可以递归调用,不会触发 call stack 的 RangeError: Maximum call stack size exceeded from v8 错误
应用场景

阻止进入事件循环,即在当前上下文之后,继续事件循环之前,执行操作

微任务并不能阻塞进入事件循环

  • 执行回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const net = require('net');

    const server = net.createServer(() => { }).listen(8080);

    server.on('listening', () => {
    // 这里是监听 'listening' 事件后执行的回调函数
    console.log('已监听到!')
    });
    // 如果调用listen方法后,同步触发 listening 事件,此时尚未执行 `server.on`注册 listening 回调函数
    // 导致无法监听到服务器启动事件。因此在实现上,将触发 listening 事件的操作放在 nextTick 队列中
    // 在当前上下文执行完毕后(注册了 listening 回调),在其他 Promise 微任务之前,触发 listening 事件
    // 确保回调函数注册完成,且不影响其他Promise异步操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const EventEmitter = require('events');

    class MyEmitter extends EventEmitter {
    constructor() {
    super();
    // 等下文注册了监听函数后,再触发
    process.nextTick(() => {
    this.emit('event');
    });
    }
    }

    const myEmitter = new MyEmitter();
    myEmitter.on('event', () => {
    console.log('an event occurred!');
    });
  • 发出错误、清理资源、再次尝试请求等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function apiCall(arg, callback) {
    if (typeof arg !== 'string') {
    return process.nextTick(
    callback,
    new TypeError('argument should be string')
    );
    }
    }
    // 未进入事件循环,微任务无法执行
    Promise.resolve().then(() => {
    console.log('Promise callback execute in even-loop');
    });

    (async () => {
    console.log('main start')
    apiCall(1, (e) => {
    throw e
    })
    console.log('main end')
    })()

    // main start
    // main end
    // TypeError: argument should be string
执行顺序

可阻止进入事件循环,

  • 进入前

    当前上下文执行完后执行

  • 进入后

    在事件循环各阶段之间,microtask之前执行

vs 同步代码
  • 在当前上下文,同步代码执行完后,进入事件循环之前,立即执行
vs microtask

下面的规律基于 commonjs 模块系统,esm 模块系统中先执行microtask

  • 在 main 或 timer、nextTick回调中同时注册 nextTick 和 microtask,先执行 nextTick 的回调

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    setTimeout(() => {
    console.log('start');
    Promise.resolve().then(() => {
    console.log('Promise callback1 executed');
    })
    process.nextTick(() => {
    console.log('NextTick callback executed');
    });
    Promise.resolve().then(() => {
    console.log('Promise callback2 executed');
    })
    console.log('end')
    }, 0);
    // start
    // end
    // NextTick callback executed
    // Promise callback1 executed
    // Promise callback2 executed
  • 在 microtask 回调中同时注册 nextTick 和 microtask,先执行 microtask,队列清空后再立即执行 nextTick(事件循环进入下一阶段之前)

    参考 Process.nextTick and Promise callback

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Promise.resolve().then(() => {
    console.log('start');
    Promise.resolve().then(() => {
    console.log('Promise callback1 executed');
    })
    process.nextTick(() => {
    console.log('NextTick callback executed');
    });
    Promise.resolve().then(() => {
    console.log('Promise callback2 executed');
    })
    console.log('end')
    });
    // start
    // end
    // Promise callback1 executed
    // Promise callback2 executed
    // NextTick callback executed
vs 事件循环
  • 在事件循环的每个阶段之间执行,直至 nextTick queue 和 microtask queue 为空

    在 nextTick 中添加的 promise,或 promise 中添加的 nextTick 也会执行完再进入下一个阶段

  • timer(setTimeout、setInterval)

    node 10,先执行完 timer 队列,再执行 nextTick 和 microtask

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    setTimeout(() => {
    console.log('timer1')
    Promise.resolve().then(function () {
    console.log('promise1')
    })
    }, 0)
    setTimeout(() => {
    console.log('timer2')
    Promise.resolve().then(function () {
    console.log('promise2')
    })
    }, 0)
    // node 10 输出
    // timer1 timer2 promise1 promise2
    // node >10 输出
    // timer1 promise1 timer2 promise2

    node 11 以后,每个 timer 执行之后都会先依次执行完 nextTick queue 和 microtask queue,再执行第二个 timer,即使后面的timer已经达到。

    在 nextTick 中添加的 promise,或 promise 中添加的 nextTick 也会执行完再执行第二个 timer

    参考 run nextTicks after each immediate and timer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    console.log('main module start')
    Promise.resolve().then(function () {
    console.log('promise1')
    })
    process.nextTick(() => {
    console.log('NextTick callback1 executed');
    });
    setTimeout(() => {
    console.log('timer1')
    Promise.resolve().then(function () {
    console.log('promise2')
    })
    process.nextTick(() => {
    console.log('NextTick callback2 executed');
    });
    }, 0)
    setTimeout(() => {
    console.log('timer2')
    Promise.resolve().then(function () {
    console.log('promise3')
    })
    process.nextTick(() => {
    console.log('NextTick callback3 executed');
    });
    }, 0)
    console.log('main module end')
    // 输出结果为
    // main module start
    // main module end
    // NextTick callback1 executed
    // promise1
    // timer1
    // NextTick callback executed
    // promise1
    // timer2
    // NextTick callback executed
    // promise2
vs Promise
  • 创建一个 promise 实例会分配3个闭包,大量未完成的promise会占用较多内存

    1. 执行器函数 executor
    2. resolve 参数
    3. reject 参数

microtask queue

  • 由 v8 维护

  • 通过 Promise 和 queueMicrotask() 添加任务

    两种方式添加的微任务类型相同,按注册顺序执行

应用场景
  • 主要用于异步获取操作结果

    1. 不阻塞主线程

    2. 将操作放在当前微任务队列中异步执行

      • 不阻塞父级上下线文中同步代码的执行

        异步调用当前操作,且父级上下文中存在同步代码

        如果是await同步调用,无论怎样,当前异步操作中的回调都会先于父级上下文await下面的回调

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        async function load(key) {
        const path = pathModule.join(__dirname, 'target.txt')
        const hit = fs.existsSync(path)
        console.log('is hit', hit)
        if (hit === true) {
        // queueMicrotask(() => {
        // myEmitter.emit('load', 'load')
        // })
        myEmitter.emit('load', 'load')
        return
        }

        await new Promise(res => {
        fs.writeFile(path, 'data', { flag: 'w' }, () => {
        res()
        myEmitter.emit('load', 'load')
        })
        })
        }

        myEmitter.on('load', () => console.log('loaded data'))

        async function processData(key) {
        console.log('start...')
        load(key) // 异步调用
        // await load(key)
        console.log('finish...') // 父级上下文中的同步代码
        }
      • 维持操作的执行顺序

        当异步操作中存在同步分支时,将同步分支的操作异步执行。nextTick 也可以实现,但是先于所有 promise 执行

        使接口要么100%同步要么100%异步

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        DataHandler.prototype.load = async function load(key) {
        const hit = this._cache.get(key);
        if (hit !== undefined) {
        queueMicrotask(() => {
        this.emit('load', hit); // 确保 load 事件总是异步触发
        });
        return;
        }

        const data = await fetchData(key);
        this._cache.set(key, data);
        this.emit('load', data); // 异步触发
        };
    3. 优化并发控制

      事件循环的一个迭代期内对异步操作的多次调用,在一个微任务中批量执行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const messageQueue = [];

      let sendMessage = (message) => {
      messageQueue.push(message);
      // 只有第一个加入队列的消息会注册一个微任务,后续消息直接加入队列
      if (messageQueue.length === 1) {
      // 在微任务注册到执行这段时间内发送的消息,通过一个微任务执行
      queueMicrotask(() => {
      const json = JSON.stringify(messageQueue);
      // 微任务执行后,将队列清空
      messageQueue.length = 0;
      fetch("url-of-receiver", json);
      });
      }
      };

      参考

执行顺序
vs 同步代码
  • 同步代码执行完毕,退出ECStack,进入事件循环后执行
vs nextTick
  • [参考](#vs microtask)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    console.log('main start')

    process.nextTick(() => {
    console.log('register in main, execute nextTick')
    })
    queueMicrotask(() => {
    console.log('register in main, execute queueMicrotask')
    })

    setTimeout(() => {
    queueMicrotask(() => {
    console.log('register in timer, execute queueMicrotask 2')
    })
    process.nextTick(() => {
    console.log('register in timer, execute nextTick 2')
    })
    }, 0)

    queueMicrotask(() => {
    queueMicrotask(() => {
    console.log('register in microtask, execute queueMicrotask 2')
    })
    process.nextTick(() => {
    console.log('register in microtask, execute nextTick 2')
    })
    })

    process.nextTick(() => {
    queueMicrotask(() => {
    console.log('register in nextTick, execute queueMicrotask 3')
    })
    process.nextTick(() => {
    console.log('register in nextTick, execute nextTick 3')
    })
    })

    console.log('main end')

    // commonjs 模块中
    // main start
    // main end
    // register in main, execute nextTick
    // register in nextTick, execute nextTick 3
    // register in main, execute queueMicrotask
    // register in nextTick, execute queueMicrotask 3
    // register in microtask, execute queueMicrotask 2
    // register in microtask, execute nextTick 2
    // register in timer, execute nextTick 2
    // register in timer, execute queueMicrotask 2

    // esm 模块, package.json 中添加 "type": "module",
    // main start
    // main end
    // register in main, execute queueMicrotask
    // register in microtask, execute queueMicrotask 2
    // register in main, execute nextTick
    // register in microtask, execute nextTick 2
    // register in nextTick, execute nextTick 3
    // register in nextTick, execute queueMicrotask 3
    // register in timer, execute nextTick 2
    // register in timer, execute queueMicrotask 2
vs 事件循环
  • [参考](# vs 事件循环)