Node.js provides several timer functions for scheduling code execution. Here's how they work.
setTimeout#
1// Basic timeout
2const timeoutId = setTimeout(() => {
3 console.log('Executed after 1 second');
4}, 1000);
5
6// With arguments
7setTimeout((name, greeting) => {
8 console.log(`${greeting}, ${name}!`);
9}, 1000, 'Alice', 'Hello');
10
11// Clear timeout
12clearTimeout(timeoutId);
13
14// Zero delay (not immediate!)
15setTimeout(() => {
16 console.log('After I/O');
17}, 0);
18
19console.log('Before timeout');
20// Output:
21// Before timeout
22// After I/OsetInterval#
1// Repeated execution
2let count = 0;
3const intervalId = setInterval(() => {
4 count++;
5 console.log(`Count: ${count}`);
6
7 if (count >= 5) {
8 clearInterval(intervalId);
9 console.log('Interval cleared');
10 }
11}, 1000);
12
13// Polling pattern
14function pollStatus() {
15 const intervalId = setInterval(async () => {
16 const status = await checkStatus();
17
18 if (status === 'complete') {
19 clearInterval(intervalId);
20 console.log('Task complete!');
21 }
22 }, 2000);
23
24 return intervalId;
25}
26
27// Clear all intervals on shutdown
28const intervals = [];
29intervals.push(setInterval(() => {}, 1000));
30intervals.push(setInterval(() => {}, 2000));
31
32process.on('SIGINT', () => {
33 intervals.forEach(clearInterval);
34 process.exit(0);
35});setImmediate#
1// Execute after I/O events
2setImmediate(() => {
3 console.log('Immediate callback');
4});
5
6console.log('Synchronous');
7
8// Output:
9// Synchronous
10// Immediate callback
11
12// Compare with setTimeout(0)
13setTimeout(() => {
14 console.log('setTimeout');
15}, 0);
16
17setImmediate(() => {
18 console.log('setImmediate');
19});
20
21// Order varies! But inside I/O callback:
22const fs = require('fs');
23
24fs.readFile(__filename, () => {
25 setTimeout(() => {
26 console.log('timeout');
27 }, 0);
28
29 setImmediate(() => {
30 console.log('immediate');
31 });
32});
33// Inside I/O: immediate always before timeout
34
35// Clear immediate
36const immediateId = setImmediate(() => {});
37clearImmediate(immediateId);process.nextTick#
1// Execute before any I/O
2process.nextTick(() => {
3 console.log('nextTick callback');
4});
5
6console.log('Synchronous');
7
8// Output:
9// Synchronous
10// nextTick callback
11
12// Priority order
13setTimeout(() => console.log('setTimeout'), 0);
14setImmediate(() => console.log('setImmediate'));
15process.nextTick(() => console.log('nextTick'));
16console.log('sync');
17
18// Output:
19// sync
20// nextTick
21// setTimeout (or setImmediate)
22// setImmediate (or setTimeout)
23
24// Recursive nextTick can starve I/O!
25// Avoid this:
26function recursiveNextTick() {
27 process.nextTick(recursiveNextTick);
28}
29// This blocks the event loop!
30
31// Better: use setImmediate for recursion
32function recursiveImmediate() {
33 setImmediate(recursiveImmediate);
34}Timer Promises (Node.js 16+)#
1const { setTimeout, setInterval, setImmediate } = require('timers/promises');
2
3// Async setTimeout
4async function delay() {
5 await setTimeout(1000);
6 console.log('1 second passed');
7}
8
9// With value
10async function delayedValue() {
11 const result = await setTimeout(1000, 'delayed result');
12 console.log(result); // 'delayed result'
13}
14
15// Async setImmediate
16async function immediate() {
17 await setImmediate();
18 console.log('After I/O events');
19}
20
21// Async setInterval (returns async iterator)
22async function poll() {
23 let count = 0;
24
25 for await (const _ of setInterval(1000)) {
26 count++;
27 console.log(`Tick ${count}`);
28
29 if (count >= 5) {
30 break;
31 }
32 }
33}
34
35// With AbortController
36async function cancellableDelay() {
37 const controller = new AbortController();
38
39 setTimeout(() => controller.abort(), 500);
40
41 try {
42 await setTimeout(1000, 'result', { signal: controller.signal });
43 } catch (err) {
44 if (err.name === 'AbortError') {
45 console.log('Timeout was aborted');
46 }
47 }
48}Event Loop Phases#
1/*
2Event Loop Phases:
31. timers: setTimeout, setInterval callbacks
42. pending callbacks: I/O callbacks deferred to next loop
53. idle, prepare: internal use
64. poll: retrieve new I/O events
75. check: setImmediate callbacks
86. close callbacks: socket.on('close')
9
10process.nextTick runs between phases!
11*/
12
13// Demonstrating phases
14const fs = require('fs');
15
16fs.readFile(__filename, () => {
17 // We're in the poll phase
18
19 // Runs in check phase (after poll)
20 setImmediate(() => console.log('1. setImmediate'));
21
22 // Runs in next iteration's timers phase
23 setTimeout(() => console.log('2. setTimeout'), 0);
24
25 // Runs immediately after current operation
26 process.nextTick(() => console.log('0. nextTick'));
27});
28
29// Output:
30// 0. nextTick
31// 1. setImmediate
32// 2. setTimeoutPatterns#
1// Debounce
2function debounce(fn, delay) {
3 let timeoutId;
4
5 return function (...args) {
6 clearTimeout(timeoutId);
7 timeoutId = setTimeout(() => fn.apply(this, args), delay);
8 };
9}
10
11const debouncedSave = debounce(save, 300);
12
13// Throttle
14function throttle(fn, interval) {
15 let lastTime = 0;
16
17 return function (...args) {
18 const now = Date.now();
19
20 if (now - lastTime >= interval) {
21 lastTime = now;
22 fn.apply(this, args);
23 }
24 };
25}
26
27const throttledUpdate = throttle(update, 100);
28
29// Retry with exponential backoff
30async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
31 for (let attempt = 0; attempt <= maxRetries; attempt++) {
32 try {
33 return await fn();
34 } catch (error) {
35 if (attempt === maxRetries) {
36 throw error;
37 }
38
39 const delay = baseDelay * Math.pow(2, attempt);
40 console.log(`Retry in ${delay}ms...`);
41 await new Promise(resolve => setTimeout(resolve, delay));
42 }
43 }
44}
45
46// Timeout wrapper
47function withTimeout(promise, ms) {
48 const timeout = new Promise((_, reject) => {
49 setTimeout(() => reject(new Error('Timeout')), ms);
50 });
51
52 return Promise.race([promise, timeout]);
53}
54
55// Usage
56const result = await withTimeout(fetchData(), 5000);Timer References#
1// Timers keep process alive by default
2const timer = setTimeout(() => {}, 100000);
3// Process won't exit until timer fires or is cleared
4
5// unref() allows process to exit
6timer.unref();
7// Process can exit even with timer pending
8
9// ref() restores default behavior
10timer.ref();
11// Process waits for timer again
12
13// Common pattern: keep-alive with unref
14function keepAlive() {
15 const interval = setInterval(() => {
16 sendHeartbeat();
17 }, 30000);
18
19 // Don't prevent shutdown
20 interval.unref();
21
22 return interval;
23}
24
25// Check if timer is active
26const { hasRef } = timer;
27console.log(hasRef()); // true or falseScheduling Patterns#
1// Run once after event loop clears
2setImmediate(() => {
3 console.log('All sync code done');
4});
5
6// Ensure async behavior
7function ensureAsync(callback) {
8 process.nextTick(callback);
9}
10
11// API that's always async
12function getData(callback) {
13 if (cache) {
14 // Don't call synchronously!
15 process.nextTick(() => callback(null, cache));
16 return;
17 }
18
19 fetchFromDB((err, data) => {
20 cache = data;
21 callback(err, data);
22 });
23}
24
25// Batch processing with setImmediate
26async function processBatch(items) {
27 const results = [];
28
29 for (let i = 0; i < items.length; i++) {
30 results.push(processItem(items[i]));
31
32 // Yield to event loop every 100 items
33 if (i % 100 === 0) {
34 await new Promise(setImmediate);
35 }
36 }
37
38 return results;
39}Common Mistakes#
1// Wrong: Relying on timer precision
2// Timers are not precise, minimum delay varies
3
4// Wrong: setTimeout(0) vs setImmediate
5// Order is not guaranteed outside I/O callbacks
6
7// Wrong: Recursive nextTick
8// Can starve I/O, use setImmediate instead
9
10// Wrong: Not clearing timers
11// Memory leak if component unmounts/restarts
12
13// Wrong: Using this in timer callback
14const obj = {
15 value: 42,
16 wrong: function() {
17 setTimeout(function() {
18 console.log(this.value); // undefined!
19 }, 100);
20 },
21 right: function() {
22 setTimeout(() => {
23 console.log(this.value); // 42
24 }, 100);
25 }
26};Best Practices#
Choosing Timers:
✓ setTimeout for delayed execution
✓ setInterval for repeated tasks
✓ setImmediate after I/O
✓ process.nextTick for immediate async
Memory Management:
✓ Always clear timers when done
✓ Use unref() for non-critical timers
✓ Store timer IDs for cleanup
✓ Clear on process exit
Patterns:
✓ Use timer promises in Node 16+
✓ Implement debounce/throttle
✓ Add timeout to async operations
✓ Yield with setImmediate in loops
Avoid:
✗ Recursive process.nextTick
✗ Relying on timer order
✗ Forgetting to clear intervals
✗ Assuming timer precision
Conclusion#
Node.js timers provide flexible scheduling options. Use setTimeout for delays, setInterval for repetition, setImmediate for post-I/O execution, and process.nextTick for immediate callbacks. The timer promises API in Node.js 16+ makes async timer code cleaner. Always clean up timers to prevent memory leaks.