Table of Contents
Understanding Asynchronous Programming
Node.js is built around asynchronous, non-blocking I/O operations. This means that Node.js can handle multiple operations concurrently without waiting for each operation to complete.
The Event Loop
The event loop is the heart of Node.js. It allows Node.js to perform non-blocking I/O operations by offloading operations to the system kernel whenever possible.
Callbacks
Callbacks are functions passed as arguments to other functions and executed after an operation completes:
// Synchronous operation (blocking)
console.log("Start");
console.log("Middle");
console.log("End");
// Asynchronous operation (non-blocking)
console.log("Start");
setTimeout(() => {
console.log("This runs after 2 seconds");
}, 2000);
console.log("End");
File System Operations
const fs = require("fs");
// Asynchronous file reading
fs.readFile("data.txt", "utf8", (err, data) => {
if (err) {
console.error("Error reading file:", err);
return;
}
console.log("File content:", data);
});
console.log("This runs immediately");
// Synchronous file reading (blocking)
try {
const data = fs.readFileSync("data.txt", "utf8");
console.log("Synchronous read:", data);
} catch (err) {
console.error("Error:", err);
}
Promises
Promises provide a cleaner way to handle asynchronous operations:
const fs = require("fs").promises;
// Using Promises
function readFilePromise(filename) {
return fs.readFile(filename, "utf8");
}
readFilePromise("data.txt")
.then(data => {
console.log("File content:", data);
})
.catch(err => {
console.error("Error:", err);
});
// Promise chaining
readFilePromise("file1.txt")
.then(data1 => {
console.log("File 1:", data1);
return readFilePromise("file2.txt");
})
.then(data2 => {
console.log("File 2:", data2);
})
.catch(err => {
console.error("Error:", err);
});
Async/Await
Async/await makes asynchronous code look and behave more like synchronous code:
const fs = require("fs").promises;
async function readFiles() {
try {
const data1 = await fs.readFile("file1.txt", "utf8");
console.log("File 1:", data1);
const data2 = await fs.readFile("file2.txt", "utf8");
console.log("File 2:", data2);
return "All files read successfully";
} catch (err) {
console.error("Error reading files:", err);
throw err;
}
}
// Using the async function
readFiles()
.then(result => console.log(result))
.catch(err => console.error("Function error:", err));
Practical Example: Multiple API Calls
// Simulating API calls with setTimeout
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: `User ${id}`, email: `user${id}@example.com` });
} else {
reject(new Error("Invalid user ID"));
}
}, 1000);
});
}
async function getUserData() {
try {
console.log("Fetching users...");
// Sequential execution
const user1 = await fetchUser(1);
const user2 = await fetchUser(2);
console.log("Users:", [user1, user2]);
// Parallel execution
const [user3, user4] = await Promise.all([
fetchUser(3),
fetchUser(4)
]);
console.log("More users:", [user3, user4]);
} catch (err) {
console.error("Error:", err);
}
}
getUserData();
Exercise
Create an async function that:
- Reads multiple text files concurrently
- Processes the content (count words, lines)
- Writes a summary to a new file
- Handles errors gracefully
What's Next?
In the next lesson, we'll explore file system operations in detail and learn to work with files and directories.