timer
Node.js provides timer functions such as 'setTimeout', 'setInterval', and 'setImmediate'. These share the same API as browser JavaScript, but understanding the execution order relative to Node.js-specific APIs like 'process.nextTick' and queueMicrotask is important for accurately understanding asynchronous behavior.
Timer Functions
| Function | Overview |
|---|---|
setTimeout(fn, delay) | Executes the callback once after the specified number of milliseconds. |
clearTimeout(id) | Cancels a setTimeout registration. |
setInterval(fn, delay) | Repeatedly executes the callback every specified number of milliseconds. |
clearInterval(id) | Cancels a setInterval repetition. |
setImmediate(fn) | Executes the callback immediately after the current I/O event handling completes. |
clearImmediate(id) | Cancels a callback registered with setImmediate. |
process.nextTick(fn) | Executes the callback immediately after the current operation completes, before any other I/O events. |
queueMicrotask(fn) | Adds a callback to the microtask queue. Runs after process.nextTick but before setImmediate. |
setTimeout — Execute Once After a Delay
'setTimeout()' executes a callback once after the specified number of milliseconds. Pass the returned timer ID to clearTimeout() to cancel before execution.
set_timeout.js
console.log('Okabe Rintaro: Time leap preparation started');
// Execute once after 500ms
var timerId = setTimeout(function() {
console.log('Makise Kurisu: 500ms elapsed. Executing time leap');
}, 500);
// Cancel the timer after 200ms
setTimeout(function() {
// Cancel the timer with clearTimeout()
clearTimeout(timerId);
console.log('Okabe Rintaro: Operation aborted. Time leap cancelled');
}, 200);
console.log('Okabe Rintaro: setTimeout is asynchronous — this line runs immediately');
node set_timeout.js Okabe Rintaro: Time leap preparation started Okabe Rintaro: setTimeout is asynchronous — this line runs immediately Okabe Rintaro: Operation aborted. Time leap cancelled
Even with a delay of 0, execution is not immediate. It runs in the next cycle of the event loop after the current synchronous code completes.
setInterval — Execute Repeatedly at a Fixed Interval
'setInterval()' repeatedly executes a callback every specified number of milliseconds. It can be stopped at any time with clearInterval(). Used for periodic monitoring, polling, animations, and more.
set_interval.js
var count = 0;
// Increment count every 300ms
var intervalId = setInterval(function() {
count++;
console.log('Shiina Mayuri: Divergence measurement ' + count + ' in progress...');
if (count >= 4) {
// Stop the repetition with clearInterval()
clearInterval(intervalId);
console.log('Hashida Itaru: Measurement complete. Divergence meter stopped');
}
}, 300);
console.log('Okabe Rintaro: Measurement started (this line runs first)');
node set_interval.js Okabe Rintaro: Measurement started (this line runs first) Shiina Mayuri: Divergence measurement 1 in progress... Shiina Mayuri: Divergence measurement 2 in progress... Shiina Mayuri: Divergence measurement 3 in progress... Shiina Mayuri: Divergence measurement 4 in progress... Hashida Itaru: Measurement complete. Divergence meter stopped
setImmediate — Execute After I/O Processing
'setImmediate()' executes a callback immediately after the current event loop's I/O event handling completes. Inside an I/O callback, setImmediate is guaranteed to run before setTimeout. However, at the top level (outside an I/O callback), the execution order of setImmediate and setTimeout(fn, 0) depends on the runtime environment.
set_immediate.js
var fs = require('fs');
console.log('Okabe Rintaro: 1. Synchronous processing (start)');
// setImmediate: runs immediately after I/O processing completes
setImmediate(function() {
console.log('Amane Suzuha: 3. setImmediate executed');
});
// setTimeout(fn, 0): runs in the timer phase
setTimeout(function() {
console.log('Makise Kurisu: setTimeout executed');
}, 0);
// Inside an I/O callback, setImmediate always runs before setTimeout
fs.readFile('/tmp/dummy_not_exist.txt', function() {
setImmediate(function() {
console.log('Hashida Itaru: 4. setImmediate after I/O (runs before setTimeout)');
});
setTimeout(function() {
console.log('Shiina Mayuri: 5. setTimeout after I/O');
}, 0);
});
console.log('Okabe Rintaro: 2. Synchronous processing (end)');
node set_immediate.js Okabe Rintaro: 1. Synchronous processing (start) Okabe Rintaro: 2. Synchronous processing (end) Amane Suzuha: 3. setImmediate executed Makise Kurisu: setTimeout executed Hashida Itaru: 4. setImmediate after I/O (runs before setTimeout) Shiina Mayuri: 5. setTimeout after I/O
The order of top-level setImmediate and setTimeout(fn, 0) may vary by environment, so the output above is one possible result. Inside an I/O callback (lines 4 and 5), setImmediate always runs before setTimeout.
process.nextTick / queueMicrotask — Interrupt Execution
'process.nextTick()' executes a callback immediately after the current operation completes, before the next phase of the event loop begins. The NextTick queue is processed before any I/O events or timers. 'queueMicrotask()' uses the same microtask queue as Promise.then and runs after nextTick.
next_tick.js
console.log('Okabe Rintaro: 1. Synchronous processing start');
// process.nextTick: runs immediately after current operation (fastest async)
process.nextTick(function() {
console.log('Makise Kurisu: 3. process.nextTick (before event loop starts)');
});
// queueMicrotask: runs after nextTick, before setImmediate
queueMicrotask(function() {
console.log('Amane Suzuha: 4. queueMicrotask (microtask queue)');
});
// Promise.then also goes into the microtask queue (same priority as queueMicrotask)
Promise.resolve().then(function() {
console.log('Hashida Itaru: 5. Promise.then (microtask queue)');
});
// setImmediate: after I/O phase
setImmediate(function() {
console.log('Shiina Mayuri: 6. setImmediate');
});
// setTimeout: timer phase
setTimeout(function() {
console.log('Okabe Rintaro: 7. setTimeout(fn, 0)');
}, 0);
console.log('Okabe Rintaro: 2. Synchronous processing end');
node next_tick.js Okabe Rintaro: 1. Synchronous processing start Okabe Rintaro: 2. Synchronous processing end Makise Kurisu: 3. process.nextTick (before event loop starts) Amane Suzuha: 4. queueMicrotask (microtask queue) Hashida Itaru: 5. Promise.then (microtask queue) Shiina Mayuri: 6. setImmediate Okabe Rintaro: 7. setTimeout(fn, 0)
Execution Order Summary
The execution priority in the Node.js event loop is as follows.
| Priority | Queue / Phase | Representative APIs |
|---|---|---|
| 1 | Synchronous code | Regular JavaScript code |
| 2 | NextTick queue | process.nextTick() |
| 3 | Microtask queue | queueMicrotask(), Promise.then() |
| 4 | Timer phase | setTimeout(), setInterval() |
| 5 | I/O callback phase | File and network I/O callbacks |
| 6 | Check phase | setImmediate() |
Calling process.nextTick() recursively prevents the NextTick queue from draining, causing I/O events to never be processed — a "NextTick flood". Recursive asynchronous processing often uses setImmediate() instead.
Common Mistakes
setInterval drift
When a callback takes longer to execute than the interval, the next execution overlaps and delays accumulate. This is called "drift". When you want to schedule the next call after completion, a recursive setTimeout() pattern at the end of the callback is used.
var count = 0;
// Timer pattern that does not drift
function tick() {
count++;
console.log('Okabe Rintaro: Measurement ' + count);
if (count < 4) {
// Schedule next call after callback completes — no drift
setTimeout(tick, 300);
}
}
setTimeout(tick, 300);
Recursive process.nextTick calls stalling I/O processing
Continuously calling process.nextTick() recursively inside a callback keeps the NextTick queue from ever being empty, so I/O events are never processed — a "NextTick flood".
// NG: recursive process.nextTick prevents I/O from ever being processed
function infiniteNextTick() {
process.nextTick(function() {
console.log('Makise Kurisu: nextTick keeps being called...');
infiniteNextTick(); // recursive call
});
}
infiniteNextTick();
// I/O events are never processed
In such cases, use setImmediate() instead of process.nextTick(). Because setImmediate() runs after I/O event processing, no flood occurs.
Overview
All Node.js timers are asynchronous and are not executed at the moment of registration. They are processed in order during each phase of the event loop after the current synchronous code finishes. The execution order of setTimeout(fn, 0) and setImmediate() outside of I/O callbacks may vary by environment. To guarantee order, use them inside an I/O callback or explicitly control priority.
For repeated processing, setInterval() is convenient, but if the callback takes longer than the interval, the next execution may overlap causing accumulated delays. When you want to schedule the next execution after completion, a recursive setTimeout() at the end of the callback is used.
process.nextTick() runs before Promises, making it useful for scenarios where you want to fire an event immediately after object initialization (such as calling EventEmitter emit inside a constructor). However, overuse can delay I/O processing, so it is best to use it sparingly.
If you find any errors or copyright issues, please contact us.