Handling Errors in Asynchronous Code in Node.js

 

Ah, JavaScript errors… The uninvited guests of our code.

You’re happily coding your Node.js application, everything seems perfect, and then... BOOM!

UnhandledPromiseRejectionWarning: Error: Something went wrong!

What just happened?!

Don’t worry! In this guide, you’ll learn:

  • Why error handling in async code is tricky.
  • How to gracefully handle errors in callbacks, Promises, and Async/Await.
  • Best practices to keep your app from exploding unexpectedly.

Grab some, and let’s fix these errors!

The Problem: Errors in Asynchronous Code

Error handling in synchronous code is easy:

try {
    let result = 10 / 0; 
    console.log(result); // Infinity, but no crash!
} catch (error) {
    console.log("Oops! Error:", error);
}

But in asynchronous code, things get messy.

setTimeout(() => {
    throw new Error("Oops! Something went wrong!");
}, 1000);

This will crash your entire app!

Handling Errors in Callbacks (a.k.a. The Old Way)

Before Promises, we handled errors using error-first callbacks:

const readFile = (filename, callback) => {
    setTimeout(() => {
        let error = Math.random() > 0.5 ? new Error("File not found!") : null;
        let data = error ? null : "File content here...";
        callback(error, data);
    }, 1000);
};

// Using error-first callback
readFile("data.txt", (err, data) => {
    if (err) {
        console.log("Error:", err.message);
    } else {
        console.log("Success:", data);
    }
});

Output (Randomly succeeds or fails):

Error: File not found!

OR

Success: File content here...

Problems with this approach:

  • Too many nested callbacks lead to Callback Hell.
  •  Error handling spreads all over the codebase.

Solution? Use Promises!

 

Handling Errors in Promises (Better, but Still Not Perfect)

Promises reject when something goes wrong:

const readFilePromise = (filename) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let error = Math.random() > 0.5 ? new Error("File not found!") : null;
            error ? reject(error) : resolve("File content here...");
        }, 1000);
    });
};

// Handling with .catch()
readFilePromise("data.txt")
    .then(data => console.log("Success:", data))
    .catch(err => console.log("Error:", err.message));

Output:

Error: File not found!

OR

Success: File content here...

Key Benefits of Promises:

  • No more nested callbacks!
  • Errors bubble up to .catch() automatically.

But there’s still a better way… Async/Await!

 

Handling Errors in Async/Await (The Best Way!)

Async/Await makes error handling simple with try/catch:

async function fetchFile() {
    try {
        let data = await readFilePromise("data.txt");
        console.log("Success:", data);
    } catch (error) {
        console.log("Error:", error.message);
    }
}

fetchFile();

Output:

Error: File not found!

OR

Success: File content here...

Why Async/Await is the best?

  • Looks like synchronous code, easy to read.
  • Handles errors cleanly with try/catch.

Unhandled Promise Rejections (The Silent Killers ☠️)

Biggest mistake in Promises? Not handling rejections!

readFilePromise("data.txt"); // NO .catch() 

This will cause an unhandled promise rejection!

Solution: Use process.on() to catch ALL promise rejections globally:

process.on("unhandledRejection", (reason, promise) => {
    console.log("Unhandled Promise Rejection:", reason.message);
});

Best Practice: Always chain .catch() in Promises or use try/catch in Async/Await!

 

Handling Multiple Errors with Promise.all()

What happens if multiple async calls fail?

const p1 = readFilePromise("file1.txt");
const p2 = readFilePromise("file2.txt");

Promise.all([p1, p2])
    .then(results => console.log("Success:", results))
    .catch(error => console.log("Oops! One failed:", error.message));

If one fails, the entire Promise.all() fails!

Solution? Use Promise.allSettled()!

Promise.allSettled([p1, p2])
    .then(results => console.log("Results:", results));

Output:

Results: [
    { status: 'fulfilled', value: 'File content here...' },
    { status: 'rejected', reason: Error: File not found! }
]

Best for APIs where some requests may fail, but you still want the rest!

 

Graceful Error Handling (Don’t Crash Your App!)

Best practices to prevent Node.js crashes:

Use try/catch in async functions:

async function safeFetch() {
    try {
        let data = await readFilePromise("data.txt");
        console.log("File:", data);
    } catch (error) {
        console.error("Handled Error:", error.message);
    }
}

Catch ALL unhandled errors globally:

process.on("uncaughtException", (error) => {
    console.error("Uncaught Exception:", error.message);
});

Catch ALL unhandled promise rejections globally:

process.on("unhandledRejection", (reason) => {
    console.error("Unhandled Rejection:", reason.message);
});

Use logging tools like Winston or Sentry for monitoring!

 

Conclusion

Now you’re an error-handling pro in Node.js!

  • You know how to handle errors in callbacks, Promises, and Async/Await.
  • You understand unhandled promise rejections and how to catch them.
  • You can use Promise.allSettled() to handle multiple async calls safely.
  • You’ve learned best practices to prevent your Node.js app from crashing.

 Now go write bug-free async code! Happy coding!

 

Post a Comment

0 Comments