簡體   English   中英

對 Nodejs 事件循環中的計時器和微任務感到困惑

[英]Confused about timers and microtasks in Nodejs event loop

我對下面代碼的 output 感到困惑。 (取自 IBM 開發人員 Nodejs 課程)。

'use strict'

const logger = require('../common/logger');

const { MAINLINE, START, END } = require('../common/constants');

(function mainline() {
    logger.info(START, MAINLINE);
    
    process.nextTick(() => {
        logger.info('mainline:process.nextTick() says: hello!', 'MICROTASK')
    });

    let iteration = 0;
    let intervalTimeout = setInterval(() => {
        if (iteration < 3) {
            setTimeout((iteration) => {
                logger.info('setInterval(' + iteration + '):setTimeout() says: Timer expired!', 'TIMERS');
                process.nextTick((iteration) => {
                    logger.info('setInterval():setTimeout(' + iteration + '):process.nextTick() says: Delimit TIMERS phase!', 'MICROTASK');
                }, iteration);
            }, 0, iteration);
        } else {
            logger.info('setInterval(' + iteration + ') says: Goodbye!', 'TIMERS');
            clearInterval(intervalTimeout);
        }
        iteration++;
    });

    logger.info(END, MAINLINE)
})();

output如下:

1593574413204:INFO: MAINLINE: START
1593574413206:INFO: MAINLINE: END
1593574413207:INFO: MICROTASK: mainline:process.nextTick() says: hello!
1593574413210:INFO: TIMERS: setInterval(0):setTimeout() says: Timer expired!
1593574413210:INFO: MICROTASK: setInterval():setTimeout(0):process.nextTick() says: Delimit TIMERS phase!
1593574413211:INFO: TIMERS: setInterval(1):setTimeout() says: Timer expired!
1593574413211:INFO: MICROTASK: setInterval():setTimeout(1):process.nextTick() says: Delimit TIMERS phase!
1593574413213:INFO: TIMERS: setInterval(2):setTimeout() says: Timer expired!
1593574413213:INFO: TIMERS: setInterval(3) says: Goodbye!
1593574413213:INFO: MICROTASK: setInterval():setTimeout(2):process.nextTick() says: Delimit TIMERS phase!

為什么記錄器在最后一個計時器微任務之前記錄“再見”?

也許如果您使用遞歸 setTimeout 而不是 setInterval 重寫此代碼,這會使發生的事情更清楚。

那會給出類似的東西

(function mainline() {

  let iteration = 0;
  function intervalJob() {
    if (iteration < 3) {
      setTimeout(timeoutJob, 0, interation);
      // reproduce what setInterval does using recursive setTimeout
      setTimeout(intervalJob, 0);
    }
    else {
      logger.info('setInterval(' + iteration + ') says: Goodbye!', 'TIMERS');
    }
    iteration++;
  }
  function timeoutJob(iteration) {
    logger.info('setInterval(' + iteration + '):setTimeout() says: Timer expired!', 'TIMERS');
    process.nextTick(microtaskJobFromTimeout, iteration);
  }
  function microtaskJobFromTimeout(iteration) {
    logger.info('setInterval():setTimeout(' + iteration + '):process.nextTick() says: Delimit TIMERS phase!', 'MICROTASK');
  }

  logger.info("START", "MAINLINE");
  
  process.nextTick(() => {
    console.log('mainline:process.nextTick() says: hello!', 'MICROTASK')
  });
  
  setTimeout(intervalJob,0);
  
  logger.info("END", "MAINLINE")

})();

基本上,在iteration達到3之前,每個intervalJob調用將安排兩個新作業在下一次事件循環迭代中執行: timeoutJob ,它本身將在內部安排一個微任務,以及遞歸的intervalJob

因為節點的事件循環首先執行所有超時作業,然后只執行所有微任務,所以最后一個intervalJob實際上將在與最后一個timeoutJob相同的事件循環迭代期間位於超時池中。

所以timeoutJob通常在之前執行,因為它首先被調度,然后是intervalJob因為它也在計時器池中,最后是微任務,在循環重復之前。

您可以通過從setInterval回調無條件記錄來驗證這一點,output 將是:

主線:開始
主線:結束
MICROTASK: mainline:process.nextTick() 說:你好!
INTERVALJOB:無條件間隔作業記錄
TIMERS: setInterval(0):setTimeout() 說:定時器過期了!
INTERVALJOB:無條件間隔作業記錄
MICROTASK: setInterval():setTimeout(0):process.nextTick() 說:定界定時器階段!
TIMERS: setInterval(1):setTimeout() 說:定時器過期了!
INTERVALJOB:無條件間隔作業記錄
MICROTASK: setInterval():setTimeout(1):process.nextTick() 說:定界定時器階段!
TIMERS: setInterval(2):setTimeout() 說:定時器過期了!
計時器:setInterval(3) 說:再見!
INTERVALJOB:無條件間隔作業記錄
MICROTASK: setInterval():setTimeout(2):process.nextTick() 說:定界定時器階段!

請注意,這是特定於節點的行為,在瀏覽器中,每個任務都有自己的微任務檢查點,並且計划同時觸發的兩個計時器實際上將在兩個不同的事件循環迭代中執行

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM