Learnitweb

Handling Errors in Asynchronous Code

In this section, we are going to understand how errors are handled in asynchronous JavaScript code.

So far, we have seen how callbacks work and how asynchronous execution is handled using the event loop. Now we’ll focus on what happens when something goes wrong inside an asynchronous operation and why traditional try...catch does not work in such cases.

Creating an Asynchronous Function

Let’s begin by creating a function that calculates the square of a number.
To simulate a real-world asynchronous operation, we will use setTimeout.

Step 1: Basic Asynchronous Function

function calculateSquare(number, callback) {
    setTimeout(() => {
        const result = number * number;
        callback(result);
    }, 1000);
}

What this function does:

  • It takes two arguments:
    • number → the input value
    • callback → a function that will receive the result
  • The calculation is delayed by 1 second using setTimeout
  • The callback is executed once the computation finishes

At this point, everything works asynchronously, but we have no error handling yet.

What If Something Goes Wrong?

Now imagine someone calls this function incorrectly:

calculateSquare("hello", (result) => {
    console.log(result);
});

This will produce unexpected results because "hello" * "hello" is not valid.

So naturally, we want to throw an error.

Why try...catch Does NOT Work Here

Let’s attempt to use try...catch.

try {
    calculateSquare("hello", (result) => {
        console.log(result);
    });
} catch (error) {
    console.log("Error caught:", error.message);
}

You might expect the error to be caught — but it won’t be.

Why?

Because:

  • setTimeout schedules the callback for later
  • The try...catch block finishes execution before the callback runs
  • When the callback finally executes, the call stack that contained try...catch no longer exists

This means the error becomes uncaught.

The Correct Way: Error-First Callback Pattern

To handle errors in asynchronous callbacks, JavaScript uses a well-known convention called the error-first callback pattern.

The rule:

The first argument of the callback is reserved for an error.
If there is no error, it should be null.

Updated Asynchronous Function (With Error Handling)

function calculateSquare(number, callback) {
    setTimeout(() => {
        if (typeof number !== "number") {
            callback(new Error("Input must be a number"), null);
            return;
        }

        const result = number * number;
        callback(null, result);
    }, 1000);
}

Using the Function (With Error Handling)

calculateSquare("hello", (error, result) => {
    if (error) {
        console.log("Error:", error.message);
        return;
    }

    console.log("Result:", result);
});

This follows the error-first callback pattern, which means:

  • The first parameter is reserved for an error
  • The second parameter contains the successful result

This is a very common convention in JavaScript, especially in Node.js.

Output:

Error: Input must be a number

Valid Input Example

calculateSquare(2, (error, result) => {
    if (error) {
        console.log("Error:", error.message);
        return;
    }

    console.log("Result:", result);
});

Output:

Result: 4

Why This Pattern Works

  • The callback is always executed asynchronously
  • Errors are passed explicitly, not thrown
  • The caller decides how to handle success or failure
  • The program does not crash unexpectedly

This pattern was the foundation of asynchronous JavaScript before Promises and async/await.