JavaScript is a flexible language but with great flexibility comes responsibility. Errors can occur anywhere in your code due to user input, API calls, async logic or browser limitations. Proper error handling is essential to prevent crashes and improve user experience. In this article you will learn several error handling patterns that will help you build more stable and maintainable JavaScript applications.
β οΈ The Basics: try/catch
The try/catch
block is the fundamental tool for catching runtime exceptions.
try {
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error("Something went wrong", error);
}
Use this pattern when working with code that might throw an error immediately, such as parsing JSON or calling functions that throw.
π₯ Handling Async Errors
Asynchronous functions require special attention. Errors in promises need to be caught using .catch()
or try/catch
in async
functions.
Using .catch()
fetch("/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Fetch failed", error));
Using async/await
async function loadData() {
try {
const response = await fetch("/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error loading data", error);
}
}
This approach is cleaner and easier to read.
π― Fallbacks and Defaults
If your code depends on external data or uncertain conditions, always provide a fallback:
function getUserName(user) {
return user && user.name ? user.name : "Guest";
}
Or using optional chaining:
const name = user?.name ?? "Guest";
This avoids throwing errors due to undefined or null values.
π§± Guard Clauses
Guard clauses are used to validate inputs early and exit if something is wrong:
function process(user) {
if (!user) {
console.warn("User is required");
return;
}
// continue with processing
}
This prevents deeper errors and keeps your logic safe and readable.
πΈοΈ Global Error Handling
Browsers allow you to catch unhandled errors globally:
Synchronous Errors
window.onerror = function(message, source, lineno, colno, error) {
console.error("Global error:", message);
};
Unhandled Promise Rejections
window.onunhandledrejection = function(event) {
console.error("Unhandled promise rejection:", event.reason);
};
These handlers can be useful for logging and monitoring.
π§Ό Custom Error Classes
Creating your own error types makes it easier to identify and react to specific problems:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateInput(input) {
if (input.length < 5) {
throw new ValidationError("Input too short");
}
}
Catch custom errors and handle them accordingly:
try {
validateInput("abc");
} catch (error) {
if (error instanceof ValidationError) {
alert(error.message);
} else {
throw error;
}
}
π‘ Centralized Error Logging
In production apps, errors should be logged for later analysis. You can use tools like:
Sentry
LogRocket
Bugsnag
Your custom logging service
Wrap logging inside a generic handler:
function handleError(error) {
console.error(error);
// send to server or monitoring tool
}
π« What to Avoid
Silencing errors without logging
Catching all errors and ignoring them
Mixing sync and async logic in
try/catch
carelesslyForgetting to handle promise rejections
β Conclusion
Error handling is not just about avoiding crashes, it is about building resilient systems. By combining try/catch
, fallback values, global handlers and logging you can confidently deal with unexpected situations. JavaScript provides all the tools but it is up to you to use them well.