Reputation: 13
I'm confused about the output of the code below. (Taken from IBM developer Nodejs course).
'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)
})();
The output is as follows:
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!
Why does the logger log "Goodbye" before the final timer microtask?
Upvotes: 1
Views: 199
Reputation: 136588
Maybe if you rewrite this code using a recursive setTimeout instead of setInterval that would make it clearer what happens.
That would give something like
(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")
})();
Basically, before iteration
reaches 3
, each intervalJob
call will schedule two new jobs to get executed at the next event loop iteration: timeoutJob
, which itself will internally schedule a microtask, and the recursive intervalJob
.
Because node's event loop does first executes all timeouts jobs, then only all microtasks, the last intervalJob
will actually be in the pool of timeouts during the same event loop iteration than the last timeoutJob
.
So timeoutJob
is normally executed before, since it was scheduled first, then intervalJob
because it was in the pool of timers too, and finally the microtask, before the loop reiterates.
You can verify this by logging unconditionally from the setInterval callback, the output would be:
MAINLINE: START
MAINLINE: END
MICROTASK: mainline:process.nextTick() says: hello!
INTERVALJOB: unconditional intervalJob logging
TIMERS: setInterval(0):setTimeout() says: Timer expired!
INTERVALJOB: unconditional intervalJob logging
MICROTASK: setInterval():setTimeout(0):process.nextTick() says: Delimit TIMERS phase!
TIMERS: setInterval(1):setTimeout() says: Timer expired!
INTERVALJOB: unconditional intervalJob logging
MICROTASK: setInterval():setTimeout(1):process.nextTick() says: Delimit TIMERS phase!
TIMERS: setInterval(2):setTimeout() says: Timer expired!
TIMERS: setInterval(3) says: Goodbye!
INTERVALJOB: unconditional intervalJob logging
MICROTASK: setInterval():setTimeout(2):process.nextTick() says: Delimit TIMERS phase!
Note that this is node specific behavior, in browsers each task will have its own microtasks checkpoint and two timers scheduled to fire at the same time will actually get executed on two different event loop iterations.
Upvotes: 1