10 JavaScript Error Handling Patterns That Actually Matter
Sep 18, 2026

Error handling is easy when the code is small.
Call a function, wrap it in try/catch, log the error, move on.
Real applications are different. Errors can appear inside promises, timers, event handlers, API calls, rendering code, or somewhere several layers below the function that started the operation.
The difficult part is rarely writing catch. It is deciding where an error belongs, who should handle it, and what should happen next.
This is Part 5 of the JS Coding Techniques series.
Previous parts:
- Part 1: 10 Modern JavaScript Tricks That Cut Boilerplate
- Part 2: 20 Modern JavaScript Tricks for Cleaner Everyday Code
- Part 3: 10 Debounce and Throttle Patterns Every JS Developer Should Know
- Part 4: 10 JavaScript Async Concurrency Tips and Common Pitfalls
Now let’s look at ten error handling patterns that are useful in everyday JavaScript.
1. Keep async error handling in one place
Promise chains have their own error handling:
fetchUser(userId)
.then((user) => {
renderUser(user);
})
.catch((error) => {
console.error("Failed to load user:", error);
});
There is nothing wrong with this approach.
But once a function already uses async/await, a try/catch block usually makes the control flow easier to follow.
async function loadUser(userId) {
try {
const user = await fetchUser(userId);
renderUser(user);
} catch (error) {
console.error("Failed to load user:", error);
}
}
This becomes especially useful when several operations belong to the same task.
async function loadDashboard(userId) {
try {
const user = await fetchUser(userId);
const projects = await fetchProjects(user.id);
renderDashboard({ user, projects });
} catch (error) {
console.error("Failed to load dashboard:", error);
}
}
One block now represents one operation from the application’s point of view.
The important detail is that rejected promises still need a consumer. If an async function rejects and nobody awaits it with error handling or attaches .catch(), the rejection becomes unhandled.
So this is risky:
loadDashboard(userId);
If the caller owns the failure, handle it there:
loadDashboard(userId).catch((error) => {
reportError(error);
});
A useful rule is simple: every promise should eventually have an owner responsible for its failure.