Node.js
Exercise 1 : Callbacks, Event Loop & Emitter
1. Chaining Callbacks
Write a Node.js program that defines a function add(a, b, callback)
which adds two numbers and returns the result via a callback. Chain this with another callback to multiply the result by 10 and log it.
// add function with a callback
function add(a, b, callback) {
const sum = a + b;
callback(sum);
}
// First callback calls the second (chaining)
add(10, 20, (val) => {
const result = val * 10;
console.log(`Result after multiplication: ${result}`);
});
// Named callback function for the add operation
function handleSum(sum) {
const result = sum * 10;
console.log(`Result after multiplication: ${result}`);
}
function add(a, b, callback) {
const sum = a + b;
callback(sum);
}
// Calling with a named callback
add(10, 20, handleSum);
2. Demonstrating Asynchronous Behavior
Create a countdown timer using setTimeout()
. Use setTimeout()
and console.log()
to demonstrate asynchronous behavior, and add another setTimeout
with 1000ms
to execute.
// Part 1: Countdown Timer
console.log("Countdown starts now!");
setTimeout( () => { console.log("3"); }, 1000);
setTimeout(() => { console.log("2"); }, 2000);
setTimeout(() => { console.log("1"); }, 3000);
setTimeout(() => { console.log("Go!"); }, 4000);
// Part 2: Demonstrating the Event Loop
console.log("\n--- Demonstrating Async ---");
console.log("Start");
setTimeout(() => {
console.log("Async Task A (executes after 1000ms)");
}, 1000);
setTimeout(() => {
console.log("Async Task B (executes after 500ms)");
}, 500);
console.log("End");
Expected Output Order for Async Demo:
Start
End
Async Task B (executes after 500ms)
Async Task A (executes after 1000ms)
3. Using the Event Emitter
Create an event emitter that emits a greet
event and logs a message. Then, emit a login
event with a username and log "<username>
has logged in".
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Register a listener for the 'greet' event
emitter.on('greet', () => {
console.log('Hello! Welcome to Node.js Events.');
});
// Register a listener for the 'login' event
emitter.on('login', (username) => {
console.log(`${username} has logged in.`);
});
// Emit the events
emitter.emit('greet');
emitter.emit('login', 'John');
emitter.on()
to register a listener (subscribe to an event).emitter.emit()
to trigger the event, passing data along with it.
Exercise 2 – Node.js: Buffers, Streams & File System
1. Reading a File
Use fs.readFile()
to read and display the contents of a file named info.txt
.
Create a file named info.txt
with some content in the same Folder
const fs = require('fs');
// Reads and displays the content of info.txt asynchronously
fs.readFile('info.txt', 'utf8', (err, data) => {
if (err) {
console.error("Error reading file:", err);
return;
}
console.log("\nContents of info.txt:");
console.log(data);
});
const fs = require('fs');
// Named callback function for reading the file
function handleFileData(err, data) {
if (err) {
console.error("Error reading file:", err);
return;
}
console.log("\nContents of info.txt:");
console.log(data);
}
// Reads and displays the content of info.txt asynchronously
fs.readFile('info.txt', 'utf8', handleFileData);
2. Writing and Reading Files
Write a program to write "Welcome to Node.js
" into a file named welcome.txt
. Then, read the content of welcome.txt
and log it using a callback.
const fs = require('fs');
// Write to the file
fs.writeFile('welcome.txt', 'Welcome to Node.js', (err) => {
if (err) {
return console.error('Error writing to file:', err);
}
console.log('File written successfully!');
// Read the file after writing is complete
fs.readFile('welcome.txt', 'utf8', (err, data) => {
if (err) {
return console.error('Error reading the file:', err);
}
console.log('File content:', data);
});
});
3. Using Readable Streams
Use a readable stream to read data.txt
and log chunks to the console. Explain the benefit of using streams over fs.readFile()
.
Create a file named data.txt
with some text.
const fs = require('fs');
const readStream = fs.createReadStream('data.txt', 'utf8');
readStream.on('data', (chunk) => {
console.log('--- New Chunk Received ---');
console.log(chunk);
});
readStream.on('end', () => {
console.log('\n--- Finished Reading File ---');
});
readStream.on('error', (err) => {
console.error('An error occurred:', err);
});
Benefit of Streams:
fs.readFile()
: Loads the entire file into RAM at once. This is simple but can cause a crash if the file is larger than the available memory.fs.createReadStream()
: Reads the file in small, manageable chunks. This is highly memory-efficient and allows you to process enormous files without issue. It's like sipping water through a straw instead of trying to drink the whole lake at once.
4. Working with Buffers
Create a buffer from the string "Node.js
" and print it in hexadecimal form. Then, modify the first letter of the buffer from "N
" to "C
" and print the result.
// Create a buffer from a string
const buffer = Buffer.from('Node.js');
// Print the buffer in hexadecimal form
console.log('Hexadecimal form:', buffer.toString('hex'));
// Modify the buffer's first byte ('N' -> 'C')
buffer[0] = 'C'.charCodeAt(0);
// Print the modified buffer as a string
console.log('Modified Buffer:', buffer.toString());
Exercise 3 – Git
1. Initializing a Repository and Making the First Commit
You are starting a personal website project. Initialize a Git repository, create an index.html
file, check the status, stage the file, commit it, and view the history.
# Create and navigate into the project directory
mkdir my-website
cd my-website
# Initialize a new Git repository
git init
# Create a basic index.html file
echo "<h1>Welcome to My Website!</h1>" > index.html
# Check the status (shows index.html as an untracked file)
git status
# Stage the new file for the next commit
git add index.html
# Commit the staged file with a descriptive message
git commit -m "Initial commit: Add index.html"
# View the commit history
git log
2. Branching and Merging
You want to build a new feature. Create a new branch feature-navbar
, add a navbar.html
file, commit the change, and then merge it back into the main
branch.
# Create a new branch and switch to it
git checkout -b feature-navbar
# Create a simple navbar.html file
echo "<nav>Home | About | Contact</nav>" > navbar.html
# Stage the new navbar file
git add navbar.html
# Commit the change on the feature branch
git commit -m "feat: Add navigation bar"
# Switch back to the main branch
git checkout main
# Merge the feature-navbar branch into main
git merge feature-navbar
# (Optional) View the log to see the merge commit
git log --oneline --graph