Events



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:

  1. GetFile(file) → Open(file) → Read(file) → Send(file)
  2. 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:

  1. Using blocking I/O library calls (e.g., write to file, connect to database).
  2. Adding built-in event listeners (e.g., http.request, server.connection).
  3. Creating custom event emitters and listeners.
  4. Using process.nextTick() to schedule work in the next event loop cycle.
  5. 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.

Chatgpt