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 valuecallback→ 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:
setTimeoutschedules the callback for later- The
try...catchblock finishes execution before the callback runs - When the callback finally executes, the call stack that contained
try...catchno 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 benull.
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.
