Node.js Beginner

Asynchronous Programming in Node.js: Understanding the Event Loop

CodingerWeb
CodingerWeb
19 views 50 min read

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:

  1. Reads multiple text files concurrently
  2. Processes the content (count words, lines)
  3. Writes a summary to a new file
  4. 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.