Using Events, Listeners, Timers, and Callbacks in Node.js
Introduction
Node.js provides high scalability and performance using an event-driven model.
Node.js applications run in a single-threaded event-driven model.
A thread pool works in the background for certain tasks, but the application itself has no direct concept of multiple threads.
Comparing Event Callbacks and Threaded Models
Threaded Web Model
In a traditional threaded web model, each request to the web server is assigned to an available thread.
The thread handles the request until the task completes and sends a response.
Example:
- Thread 1 handles file operations (GetFile → Open → Read → Send).
- Thread 2 handles database operations (GetData → ConnectDB → Query → Send).
Functions execute in sequence (linear fashion) on each thread.
Node.js Event Model
Node.js processes requests differently.
Instead of assigning a separate thread to each request, all work is placed into an event queue.
A single event loop processes these events one at a time.
When an operation involves blocking I/O, Node.js adds the task to the event queue with a callback function to be executed when the task completes.
When all events are processed, the Node.js application terminates.
Example of processing two requests:
- GetFile(file) → Open(file) → Read(file) → Send(file)
- GetData(db) → Connect(db) → Query(db) → Send(data)
Blocking I/O in Node.js
Problem
Functions that block while waiting for I/O (file read, database query, network request) stop execution on the main thread.
Examples of blocking I/O:
- Reading a file
- Querying a database
- Socket request
- Accessing remote service
Solution
Node.js uses event callbacks and thread pool to prevent blocking.
When a blocking I/O event is in the queue:
- Node.js picks a thread from the thread pool to run the task.
- This prevents the main event loop from stopping.
- The callback function adds results back to the event queue.
Adding Work to the Event Queue
In Node.js, work is added to the event queue by:
- Using blocking I/O library calls (e.g., write to file, connect to database).
- Adding built-in event listeners (e.g.,
http.request,server.connection). - Creating custom event emitters and listeners.
- Using process.nextTick() to schedule work in the next event loop cycle.
- Using timers to run tasks after a delay or at intervals.
Implementing Timers in Node.js
There are three timer types: timeout, interval, and immediate.
Timeout Timers
Used for delaying work for a set time.
Syntax:
setTimeout(callback, delayMilliseconds, [args])
Example:
setTimeout(myFunc, 1000);
Returns a timer ID which can be cancelled using:
clearTimeout(timeoutId);
Example – simple_timer.js
function simpleTimeout(consoleTimer) {
console.timeEnd(consoleTimer);
}
console.time("twoSecond");
setTimeout(simpleTimeout, 2000, "twoSecond");
console.time("oneSecond");
setTimeout(simpleTimeout, 1000, "oneSecond");
console.time("fiveSecond");
setTimeout(simpleTimeout, 5000, "fiveSecond");
console.time("50MilliSecond");
setTimeout(simpleTimeout, 50, "50MilliSecond");
Interval Timers
Used for repeating work at a fixed interval.
Syntax:
setInterval(callback, delayMilliseconds, [args])
Returns an interval ID which can be cancelled using:
clearInterval(intervalId);
Example:
var x = 0, y = 0, z = 0;
function displayValues() {
console.log("X=%d; Y=%d; Z=%d", x, y, z);
}
function updateX() { x++; displayValues(); }
function updateY() { y++; displayValues(); }
function updateZ() { z++; displayValues(); }
setInterval(updateX, 500);
setInterval(updateY, 1000);
setInterval(updateZ, 2000);
Immediate Timers
Used for scheduling work to run immediately after I/O events are processed, before timeout/interval events.
Syntax:
setImmediate(callback, [args])
Returns an ID that can be cancelled with:
clearImmediate(immediateId);
Example:
myImmediate = setImmediate(myFunc);
clearImmediate(myImmediate);
Node.js Events
Node.js is an asynchronous event-driven JavaScript runtime.
It has an events module to emit and listen for named events.
Objects that emit events are instances of EventEmitter.
Adding Event Listeners
Listeners are attached using:
addListener(eventName, callback)
.on(eventName, callback)
.once(eventName, callback)
Example:
const EventEmitter = require('events');
var eventEmitter = new EventEmitter();
eventEmitter.on('myEvent', (msg) => {
console.log(msg);
});
eventEmitter.emit('myEvent', "First event");
Removing Event Listeners
Useful methods:
listeners(eventName)setMaxListeners(n)removeListener(eventName, callback)removeAllListeners()
Example – Account Balance
var events = require('events');
function Account() {
this.balance = 0;
events.EventEmitter.call(this);
this.deposit = function(amount) {
this.balance += amount;
this.emit("balanceChanged");
};
this.withdraw = function(amount) {
this.balance -= amount;
this.emit("balanceChanged");
};
}
Account.prototype = events.EventEmitter.prototype;
function displayBalance() {
console.log("Account balance: $%d", this.balance);
}
function checkOverdraw() {
if (this.balance < 0) console.log("Account overdrawn!!!");
}
function checkGoal(acc, goal) {
if (acc.balance > goal) console.log("Goal Achieved!!!");
}
var account = new Account();
account.on("balanceChanged", displayBalance);
account.on("balanceChanged", checkOverdraw);
account.on("balanceChanged", function() { checkGoal(this, 1000); });
account.deposit(220);
account.deposit(320);
account.deposit(600);
account.withdraw(1200);
Dereferencing Timers
Example:
myInterval = setInterval(myFunc);
myInterval.unref();
myInterval.ref();
Callback Functions
A callback is executed after a task completes, preventing blocking.
Callbacks allow Node.js to process many requests without waiting for each function to finish.
Synchronous Example
const fs = require("fs");
const filedata = fs.readFileSync('inputfile1.txt');
console.log(filedata.toString());
console.log("End of Program execution");
Asynchronous Example
const fs = require("fs");
fs.readFile('inputfile1.txt', function(err, filedata) {
if (err) return console.error(err);
console.log(filedata.toString());
});
console.log("End of Program execution");
Explanation:
- Synchronous:
readFileSync()blocks execution until file read completes. - Asynchronous:
readFile()runs in background, and the callback executes after file reading finishes.


