Professional JavaScript development involves not just writing code that works, but writing code that is robust and predictable. This means learning how to anticipate, catch, and gracefully handle errors, and how to use the browser's tools to diagnose problems.
1. The try...catch...finally
Block
This structure is the cornerstone of synchronous error handling in JavaScript (and asynchronous error handling using async/await
as covered in Chapter 13).
try
: Contains the code that you want to monitor for potential errors.
catch
: Contains code that runs only if an error is thrown in the try
block. It receives the error object as an argument.
finally
: Contains code that runs always, regardless of whether an error was thrown or caught (often used for cleanup).
JavaScript
function divide(a, b) {
try {
if (b === 0) {
// Manually throw an error for a specific condition
throw new Error("Division by zero is not allowed.");
}
return a / b;
} catch (error) {
// Handle the error gracefully
console.error("An error occurred:", error.message);
// Return a safe default value
return null;
} finally {
console.log("Division attempt finished.");
}
}
console.log(divide(10, 2)); // Output: Division attempt finished. | 5console.log(divide(10, 0)); // Output: An error occurred: Division by zero is not allowed. | Division attempt finished. | null
2. Custom Errors
Instead of throwing generic Error
objects, you can create your own custom error classes that inherit from the built-in Error
class. This allows you to differentiate between types of errors (e.g., ValidationError
, NetworkError
).
JavaScript
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = 'NetworkError';
}
}
try {
throw new NetworkError('Failed to connect to API server.');
} catch (error) {
if (error.name === 'NetworkError') {
console.warn('Network issue detected. Showing offline message.');
} else {
console.error('Unhandled critical error:', error);
}
}
3. Defensive Programming and Type Checking
Defensive programming involves writing code that anticipates bad data or unexpected input. In loosely-typed JavaScript, checking the type and existence of variables is essential before operating on them.
Optional Chaining (?.
): Safely access nested properties without throwing an error if an intermediate property is null
or undefined
.
JavaScript
const user = { profile: { name: 'Alex' } };
// Before: user.address && user.address.cityconst city = user.address?.city; // city is undefined, no error thrown
Nullish Coalescing (??
): Provides a default value only if the value is strictly null
or undefined
. (It treats 0
or ''
as valid values, unlike the ||
operator).
JavaScript
const providedLimit = 0;
const limit = providedLimit ?? 100; // limit is 0 (0 is considered a valid value)
const providedName = null;
const name = providedName ?? 'Guest'; // name is 'Guest'
4. Browser DevTools for Debugging
The browser's Developer Tools (F12 or Ctrl+Shift+I) are your primary tools for debugging.
DevTools Tab | Usage |
Console | Viewing console.log() output, errors, and warnings. You can interactively run JavaScript code here. |
Sources | The Debugger. You can view your source code and set Breakpoints—lines of code where execution pauses so you can inspect variables and step through code line-by-line. |
Network | Monitoring all fetch or AJAX requests, viewing their headers, and checking response data and timing. |
Elements | Inspecting the live DOM tree and how JavaScript is manipulating it. |
Export to Sheets
Key Debugger Actions
Set a Breakpoint: Click the line number in the Sources tab.
Step Over: Execute the current line and move to the next.
Step Into: Jump inside a function call on the current line.
Watch: Monitor the value of specific variables as the code runs.