Node.js Timers Module
What is the Timers Module?
The Timers module provides functions that help schedule code execution at specific times or intervals.
Unlike browser JavaScript, Node.js timing functions are provided as part of the Timers module, though they are available globally without requiring an explicit import.
Key features include:
- Delayed execution with
setTimeout()
- Repeated execution with
setInterval()
- Immediate execution in the next event loop with
setImmediate()
- Promise-based APIs for modern async/await patterns
These capabilities are essential for building responsive applications, implementing polling, handling delayed operations, and more.
Getting Started with Timers
Here's a quick example of using the Timers module to schedule code execution:
Basic Timer Example
const { setTimeout, setInterval, setImmediate } = require('timers');
console.log('Starting timers...');
// Execute once after delay
setTimeout(() => {
console.log('This runs after 1 second');
}, 1000);
// Execute repeatedly at interval
let counter = 0;
const interval = setInterval(() => {
counter++;
console.log(`Interval tick ${counter}`);
if (counter >= 3) clearInterval(interval);
}, 1000);
// Execute in the next event loop iteration
setImmediate(() => {
console.log('This runs in the next iteration of the event loop');
});
console.log('Timers scheduled');
Run example »
Using the Timers Module
The Timers module's functions are available globally, so you don't need to require them explicitly.
However, if you want to access advanced features or for clarity, you can import the module:
const timers = require('timers');
// Or, for the promises API (Node.js 15.0.0+)
const timersPromises = require('timers/promises');
setTimeout() and clearTimeout()
The setTimeout()
function schedules execution of a callback after a specified amount of time (in milliseconds).
It returns a Timeout
object that can be used to cancel the timeout.
Common Use Cases
- Delaying execution of non-critical tasks
- Implementing timeouts for operations
- Breaking up CPU-intensive tasks
- Implementing retry logic
// Basic usage
setTimeout(() => {
console.log('This message is displayed after 2 seconds');
}, 2000);
// With arguments
setTimeout((name) => {
console.log(`Hello, ${name}!`);
}, 1000, 'World');
// Storing and clearing a timeout
const timeoutId = setTimeout(() => {
console.log('This will never be displayed');
}, 5000);
// Cancel the timeout before it executes
clearTimeout(timeoutId);
console.log('Timeout has been cancelled');
Run example »
Promise-Based setTimeout
Node.js 15.0.0 and later provide a promises-based API for timers:
const { setTimeout } = require('timers/promises');
async function delayedGreeting() {
console.log('Starting...');
// Wait for 2 seconds
await setTimeout(2000);
console.log('After 2 seconds');
// Wait for 1 second with a value
const result = await setTimeout(1000, 'Hello, World!');
console.log('After 1 more second:', result);
}
delayedGreeting().catch(console.error);
Run example »
setInterval() and clearInterval()
The setInterval()
function calls a function repeatedly at specified intervals (in milliseconds).
It returns an Interval
object that can be used to stop the interval.
Common Use Cases
- Polling for updates
- Running periodic maintenance tasks
- Implementing heartbeat mechanisms
- Updating UI elements at regular intervals
Note: The actual interval between executions may be longer than specified if the event loop is blocked by other operations.
// Basic interval
let counter = 0;
const intervalId = setInterval(() => {
counter++;
console.log(`Interval executed ${counter} times`);
// Stop after 5 executions
if (counter >= 5) {
clearInterval(intervalId);
console.log('Interval stopped');
}
}, 1000);
// Interval with arguments
const nameInterval = setInterval((name) => {
console.log(`Hello, ${name}!`);
}, 2000, 'Node.js');
// Stop the name interval after 6 seconds
setTimeout(() => {
clearInterval(nameInterval);
console.log('Name interval stopped');
}, 6000);
Run example »
Promise-Based setInterval
Using the promises API for intervals:
const { setInterval } = require('timers/promises');
async function repeatedGreeting() {
console.log('Starting interval...');
// Create an async iterator from setInterval
const interval = setInterval(1000, 'tick');
// Limit to 5 iterations
let counter = 0;
for await (const tick of interval) {
console.log(counter + 1, tick);
counter++;
if (counter >= 5) {
break; // Exit the loop, stopping the interval
}
}
console.log('Interval finished');
}
repeatedGreeting().catch(console.error);
Run example »
setImmediate() and clearImmediate()
The setImmediate()
function schedules a callback to run in the next iteration of the event loop, after I/O events but before timers.
It's similar to using setTimeout(callback, 0)
but more efficient.
When to Use setImmediate()
- When you want to execute code after the current operation completes
- To break up long-running operations into smaller chunks
- To ensure callbacks run after I/O operations complete
- In recursive functions to prevent stack overflows
console.log('Start');
setTimeout(() => {
console.log('setTimeout callback');
}, 0);
setImmediate(() => {
console.log('setImmediate callback');
});
process.nextTick(() => {
console.log('nextTick callback');
});
console.log('End');
Run example »
The execution order will typically be:
- Start
- End
- nextTick callback
- setTimeout callback or setImmediate callback (order can vary)
Note: The order of execution between setTimeout(0)
and setImmediate()
can be unpredictable when called from the main module.
However, inside an I/O callback, setImmediate()
will always execute before any timers.
Canceling an Immediate
const immediateId = setImmediate(() => {
console.log('This will not be displayed');
});
clearImmediate(immediateId);
console.log('Immediate has been cancelled');
Run example »
process.nextTick()
Although not part of the Timers module, process.nextTick()
is a related function that defers a callback until the next iteration of the event loop, but executes it before any I/O events or timers.
Key Characteristics
- Runs before any I/O events or timers
- Higher priority than
setImmediate()
- Processes all queued callbacks before the event loop continues
- Can lead to I/O starvation if overused
When to Use process.nextTick()
- To ensure a callback runs after the current operation but before any I/O
- To break up long-running operations
- To allow event handlers to be set up after an object is created
- To ensure consistent API behavior (e.g., making constructors work with or without `new`)
console.log('Start');
// Schedule three different types of callbacks
setTimeout(() => {
console.log('setTimeout executed');
}, 0);
setImmediate(() => {
console.log('setImmediate executed');
});
process.nextTick(() => {
console.log('nextTick executed');
});
console.log('End');
Run example »
Note: process.nextTick()
fires immediately on the same phase of the event loop, while setImmediate()
fires on the following iteration or 'tick' of the event loop.
Advanced Timer Patterns
Debouncing
Prevent a function from being called too frequently by delaying its execution:
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// Example usage
const handleResize = debounce(() => {
console.log('Window resized');
}, 300);
// Call handleResize() on window resize
Throttling
Limit how often a function can be called over time:
function throttle(func, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Example usage
const handleScroll = throttle(() => {
console.log('Handling scroll');
}, 200);
// Call handleScroll() on window scroll
Sequential Timeouts
Execute a series of operations with delays between them:
function sequentialTimeouts(callbacks, delay = 1000) {
let index = 0;
function next() {
if (index < callbacks.length) {
callbacks[index]();
index++;
setTimeout(next, delay);
}
}
next();
}
// Example usage
sequentialTimeouts([
() => console.log('Step 1'),
() => console.log('Step 2'),
() => console.log('Step 3')
], 1000);
Timer Behavior and Best Practices
Timer Precision and Performance
Node.js timers are not precise to the millisecond. The actual delay might be slightly longer due to:
- System load and CPU usage
- Event loop blocking operations
- Other timers and I/O operations
- System timer resolution (typically 1-15ms)
Measuring Timer Accuracy
const desiredDelay = 100; // 100ms
const start = Date.now();
setTimeout(() => {
const actualDelay = Date.now() - start;
console.log(`Desired delay: ${desiredDelay}ms`);
console.log(`Actual delay: ${actualDelay}ms`);
console.log(`Difference: ${actualDelay - desiredDelay}ms`);
}, desiredDelay);
Memory and Resource Management
Proper management of timers is crucial to prevent memory leaks and excessive resource usage:
Common Memory Leak Patterns
// Leak: Interval keeps running even if not needed
function startService() {
setInterval(() => {
fetchData();
}, 1000);
}
// Leak: Timeout with closure over large object
function processData(data) {
setTimeout(() => {
console.log('Processing complete');
// 'data' is kept in memory until the timeout fires
}, 10000, data);
}
Best Practices
- Always clear intervals and timeouts when they're no longer needed
- Store timer IDs in a way that allows for cleanup
- Be cautious with closures in timer callbacks
- Use
clearTimeout()
andclearInterval()
in cleanup functions
Remember to clear timers when they're no longer needed, especially in long-running applications, to prevent memory leaks:
// Bad practice in a server context
function startServer() {
setInterval(() => {
// This interval will run forever and prevent proper cleanup
console.log('Server is running...');
}, 60000);
}
// Better practice
function startServer() {
const intervalId = setInterval(() => {
console.log('Server is running...');
}, 60000);
// Store the interval ID for later cleanup
return {
stop: () => {
clearInterval(intervalId);
console.log('Server stopped');
}
};
}
// Example usage
const server = startServer();
// Stop the server after 3 minutes
setTimeout(() => {
server.stop();
}, 180000);
Zero-Delay Timeouts
When using setTimeout(callback, 0)
, the callback doesn't execute immediately.
It executes after the current event loop cycle completes, which can be used to "break up" CPU-intensive tasks:
function processArray(array, processFunction) {
const chunkSize = 1000;
let index = 0;
function processChunk() {
const chunk = array.slice(index, index + chunkSize);
chunk.forEach(processFunction);
index += chunkSize;
if (index < array.length) {
setTimeout(processChunk, 0); // Yield to the event loop
} else {
console.log('Processing complete');
}
}
processChunk();
}
// Example usage
const bigArray = Array(10000).fill().map((_, i) => i);
console.log('Starting processing...');
processArray(bigArray, (item) => {
// Simple processing
if (item % 5000 === 0) {
console.log(`Processed item ${item}`);
}
});
console.log('This will log before processing completes');
Run example »