The difference happens because on your device you are interpreting the script as an ES module (ESM), while the web REPL must be set to CommonJS (CJS).
In ESM, your script is ran in a global async
context, so I guess (I still have to check the sources...) the event-loop is in the microtask phase. Queuing a microtask from the microtask phase will prevent the event-loop from leaving the microtask queue, and thus your callback will be executed right away.
You can see this by running this script as ESM:
const t1 = Date.now();
const fn = () => Promise.resolve().then(() => {
if(Date.now()-t1 < 5000) {
Promise.resolve().then(fn);
}
else {
console.log("all promises done");
}
});
process.nextTick(() => console.log("tick"));
fn();
This will block the script for 5s, then print "all promises done" followed by "tick".
And the same is true for the "tick phase" (not sure about the exact nomenclature):
const t1 = Date.now();
const fn = () => process.nextTick(() => {
if(Date.now()-t1 < 5000) {
process.nextTick(fn);
}
else {
console.log("all ticks done");
}
});
Promise.resolve().then(() => console.log("promise"));
fn();
So in CJS, this script will lock for 5s, then print "all ticks done" followed by "promise".
Note that even in browsers microtasks are blocking in the same way.
So we can't really say that one has higher priority than the other, what can be said however, is that from the "timeout phase" or "network phase", the "tick phase" will be entered first. And so a script like
setImmediate(() => {
process.nextTick(()=>console.log('In nextTick'))
Promise.resolve().then(()=>console.log('In Promise'))
});
will print "In nextTick" then "In Promise", whether the main script is interpreted as ESM or CJS.