Node.js is built around asynchronous, non-blocking I/O operations. Two of the most important concepts enabling this behavior are Callbacks and Event Emitters. These two mechanisms allow developers to handle events and operations that may take time, such as reading files, making HTTP requests, or interacting with databases, without freezing the application.
In this document, we will explore:
A callback is a function passed as an argument to another function. It is called (or "called back") when an operation completes. This approach is foundational to asynchronous programming in Node.js.
function greet(name, callback) {
console.log('Hello, ' + name + '!');
callback();
}
function sayGoodbye() {
console.log('Goodbye!');
}
greet('Alice', sayGoodbye);
Output:
Hello, Alice!
Goodbye!
function processData(data, callback) {
let processed = data.toUpperCase();
callback(processed);
}
processData("nodejs", function(result) {
console.log(result);
});
Output: NODEJS
setTimeout(function() {
console.log("Executed after 2 seconds");
}, 2000);
The setTimeout() function executes the callback after the specified delay without blocking the rest of the code.
Many Node.js APIs, especially those dealing with I/O, use callbacks to return results asynchronously.
const fs = require('fs');
fs.readFile('example.txt', 'utf8', function(err, data) {
if (err) {
return console.error(err);
}
console.log(data);
});
Node.js follows the error-first callback convention where the first argument of the callback is reserved for an error object (if any), and the second argument is the data.
function doSomething(callback) {
const error = false;
if (error) {
callback("Something went wrong", null);
} else {
callback(null, "Success");
}
}
doSomething(function(err, result) {
if (err) {
return console.error(err);
}
console.log(result);
});
Callback hell refers to the situation where multiple nested callbacks make code difficult to read and maintain.
login(user, function(err, user) {
if (!err) {
getProfile(user.id, function(err, profile) {
if (!err) {
getFriends(profile.id, function(err, friends) {
if (!err) {
console.log(friends);
}
});
}
});
}
});
Node.js uses the EventEmitter class from the events module to handle events. An EventEmitter is an object that emits named events and allows functions (listeners) to be attached and invoked when those events are emitted.
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Register an event listener
emitter.on('start', () => {
console.log('Started!');
});
// Emit the event
emitter.emit('start');
Output: Started!
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('data', (message) => {
console.log(`Received: ${message}`);
});
myEmitter.emit('data', 'Hello from event!');
Output: Received: Hello from event!
emitter.on(event, listener) // Registers a listener
emitter.emit(event, [...args]) // Emits an event
emitter.once(event, listener) // Registers a one-time listener
emitter.removeListener(event, fn)// Removes a listener
emitter.removeAllListeners() // Removes all listeners
const emitter = new EventEmitter();
emitter.once('login', () => {
console.log('User logged in (only once)');
});
emitter.emit('login');
emitter.emit('login');
Output:
User logged in (only once)
emitter.on('greet', (name, age) => {
console.log(`Hello ${name}, you are ${age} years old`);
});
emitter.emit('greet', 'Alice', 30);
Output: Hello Alice, you are 30 years old
Node.js throws an error if an 'error' event is emitted without a listener.
const emitter = new EventEmitter();
emitter.on('error', (err) => {
console.error('Handled error:', err.message);
});
emitter.emit('error', new Error('Something went wrong'));
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello World');
});
server.on('request', (req, res) => {
console.log(`Request received for ${req.url}`);
});
server.listen(3000);
const fs = require('fs');
const stream = fs.createReadStream('example.txt');
stream.on('data', (chunk) => {
console.log('New chunk:', chunk.toString());
});
stream.on('end', () => {
console.log('Reading finished');
});
You can use both callbacks and event emitters in a single module for more flexibility.
const EventEmitter = require('events');
function fetchData(callback) {
const emitter = new EventEmitter();
setTimeout(() => {
emitter.emit('progress', '25%');
setTimeout(() => {
emitter.emit('progress', '50%');
setTimeout(() => {
emitter.emit('progress', '100%');
callback(null, 'Data fetched');
}, 500);
}, 500);
}, 500);
return emitter;
}
const emitter = fetchData((err, result) => {
if (err) return console.error(err);
console.log(result);
});
emitter.on('progress', (percent) => {
console.log(`Progress: ${percent}`);
});
function asyncTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Task complete');
}, 1000);
});
}
asyncTask().then(console.log);
async function run() {
let result = await asyncTask();
console.log(result);
}
run();
Callbacks and Event Emitters are powerful tools in the Node.js ecosystem. They allow developers to handle asynchronous operations and events efficiently. While callbacks form the basis of asynchronous programming, event emitters allow for scalable, modular, and decoupled event-driven systems.
Understanding when to use callbacks vs event emitters is critical:
By mastering these mechanisms, developers can build responsive, high-performance Node.js applications suitable for real-world use cases ranging from web servers to IoT systems and chat applications.
A function passed as an argument and executed later.
Runs multiple instances to utilize multi-core systems.
Reusable blocks of code, exported and imported using require() or import.
nextTick() executes before setImmediate() in the event loop.
Starts a server and listens on specified port.
Node Package Manager β installs, manages, and shares JavaScript packages.
A minimal and flexible web application framework for Node.js.
A stream handles reading or writing data continuously.
It processes asynchronous callbacks and non-blocking I/O operations efficiently.
Node.js is a JavaScript runtime built on Chrome's V8 engine for server-side scripting.
An object representing the eventual completion or failure of an asynchronous operation.
require is CommonJS; import is ES6 syntax (requires transpilation or newer versions).
Use module.exports or exports.functionName.
Variables stored outside the code for configuration, accessed using process.env.
MongoDB, often used with Mongoose for schema management.
Describes project details and manages dependencies and scripts.
Synchronous blocks execution; asynchronous runs in background without blocking.
Allows or restricts resources shared between different origins.
Use try-catch, error events, or middleware for error handling.
Provides file system-related operations like read, write, delete.
Using event-driven architecture and non-blocking I/O.
Functions in Express that execute during request-response cycle.
A set of routes or endpoints to interact with server logic or databases.
Yes, it's single-threaded but handles concurrency using the event loop and asynchronous callbacks.
Middleware to parse incoming request bodies, like JSON or form data.
Copyrights © 2024 letsupdateskills All rights reserved