Error Handling and Debugging in JavaScript
by Hexagon, 3 minutes read javascript guide
In this article, we delve into the essential aspects of error handling and debugging in JavaScript. Learn about different types of errors, advanced debugging techniques, and various error-handling mechanisms to develop robust and maintainable JavaScript applications.
Introduction
In a previous article, we touched the basics of debugging using console.log()
and console.error()
. But debugging is more than that. As your projects grow in
complexity, you'll need a broader set of tools and techniques to handle errors
and debug effectively.
Understanding Types of Errors
Syntax Errors
Syntax errors are straightforward: the code won't even run if there's a syntax issue.
let name = "John";
Runtime Errors
These errors occur while the program is running.
let x = undefinedVariable;
Logical Errors
Logical errors are tricky because the code runs without any issues, but it doesn't behave as expected. Here's an example to demonstrate this. Let's say you're writing a function to calculate the average of an array of numbers.
function calculateAverage(numbers) {
let sum = 0;
for (let i = 0; i <= numbers.length; i++) {
sum += numbers[i];
}
return sum / numbers.length;
}
const avg = calculateAverage([1, 2, 3, 4, 5]);
console.log("Average:", avg);
Here, the loop goes from 0 to numbers.length instead of numbers.length - 1. This won't throw any runtime or syntax errors, but the function will return the wrong average because numbers[numbers.length] is undefined.
Advanced console
Functions
console.table()
Displays tabular data as a table.
console.table([{ name: "John", age: 30 }, { name: "Jane", age: 25 }]);
console.group()
and console.groupEnd()
Group related messages together.
console.group("User Details");
console.log("Name: John");
console.log("Age: 30");
console.groupEnd();
console.time()
and console.timeEnd()
Time how long an operation takes.
console.time("Array initialize");
let arr = new Array(1000000);
console.timeEnd("Array initialize");
Error Handling Mechanisms
How you handle errors will vary based on whether you're using synchronous code, Promises, or async/await. Let's dive into the alternatives.
Error Handling in Synchronous Code
In synchronous code, you typically use try/catch blocks to catch exceptions. Here's a simple example:
try {
// Your code here
let x = 10;
let y = 0;
if (y === 0) {
throw new Error("Cannot divide by zero");
}
let result = x / y;
} catch (error) {
console.error(`Caught an error: ${error.message}`);
}
Error Handling with Promises
With Promises, you use .then()
for success cases and .catch()
for errors.
Here's how you can handle errors:
fetchData()
.then((data) => {
// Handle success
})
.catch((error) => {
// Handle error
console.error(`Error fetching data: ${error.message}`);
});
Error Handling with Async/Await
In async/await, you can use try/catch blocks, just like with synchronous code. The difference is you must place await inside the try block:
try {
const response = await fetchData();
// Handle success
} catch (error) {
// Handle error
console.error(`Error fetching data: ${error.message}`);
}
If you forget to use await
inside a try
/catch
block in an async
function, or deliberately don't want to wait, you should know that the function
will not behave as you might expect. Specifically, the Promise will not be
caught by the catch block, because it won't have resolved or rejected at the
time the catch block is executed.
Here's a quick example:
async function fetchData() {
// This should be awaited, but it's not.
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Something went wrong")), 1000);
});
}
async function main() {
try {
fetchData(); // Missing 'await' here
console.log("This will run.");
} catch (error) {
console.error("This will NOT run", error);
}
}
main();
In this example, 'This will run.'
will be printed, but 'This will NOT run'
will not, because the Promise rejection is not caught.
To handle this, you can explicitly catch the Promise:
async function main() {
try {
fetchData().catch((error) =>
console.error("Caught by Promise.catch", error)
);
console.log("This will still run.");
} catch (error) {
console.error("This will NOT run", error);
}
}
main();
Here, the error is caught by the .catch()
method attached to the Promise
returned by fetchData()
.
So, if you're not using await
, make sure to attach a .catch()
to the Promise
to handle any errors.
Debugging Best Practices
Use Version Control
Use a version control system like Git. If a bug appears, you can easily roll back to a previous working state.
Code Reviews
Peer reviews can catch errors and bad practices before they become part of the codebase.
Conclusion
Advanced error handling and debugging are essential for writing maintainable, robust JavaScript code. Whether you're working in the browser or on the server, these techniques will make your life easier.