Error Handling in JavaScript: Clean Code Best Practices from clean-code-javascript
Always handle errors explicitly by logging them to console.error, notifying the user, and reporting to monitoring services rather than silently swallowing exceptions.
The ryanmcdermott/clean-code-javascript repository provides authoritative guidelines for writing maintainable JavaScript, dedicating specific sections to error handling patterns that prevent hidden bugs and debugging nightmares. According to the source code analysis of README.md (lines 2003–2076), clean error handling in JavaScript revolves around two non-negotiable principles: never ignore caught errors, and never ignore rejected promises.
Core Principles of Clean Error Handling
The guide establishes that error handling must be a first-class concern in your architecture. Silent failures are considered worse than crashes because they allow corrupted state to propagate through your application undetected.
The two foundational rules are:
- Never silently swallow errors – Whether originating from a
try/catchblock or a rejected Promise, every error must be addressed. - Make the error visible and actionable – Log it for developers, surface it to users when appropriate, and report it to external monitoring services.
Handling Synchronous Errors with try/catch
When wrapping code in try { … } catch (e) { … }, the presence of the catch clause signals that an error might occur. Simply logging with console.log or doing nothing loses the stack trace in production and eliminates your ability to react to the problem later.
According to README.md lines 2009–2017, you should handle the error in a meaningful way:
- Write to
console.errorfor better visibility in DevTools - Notify the user via UI toasts, alerts, or error boundaries
- Report to a monitoring service like Sentry or New Relic
function fetchUser(id) {
try {
// Might throw (e.g., JSON.parse on malformed data)
const data = JSON.parse(database.get(id));
return data;
} catch (err) {
console.error(err); // 1️⃣ more visible than console.log
notifyUserOfError(err); // 2️⃣ UI feedback
reportErrorToService(err); // 3️⃣ telemetry
// Optional: re-throw if you want upstream callers to react
throw err;
}
}
Handling Asynchronous Errors in Promises
Promises that reject should be handled with .catch() (or a try/catch when using await). Ignoring a rejection is equivalent to swallowing a synchronous error. As noted in README.md lines 2045–2048, the same three-step handling applies to asynchronous code.
Promise chains with .catch()
getData()
.then(data => process(data))
.catch(err => {
console.error(err); // 1️⃣
notifyUserOfError(err); // 2️⃣
reportErrorToService(err); // 3️⃣
});
Async/await with centralized error handling
async function loadDashboard() {
try {
const stats = await fetchStats(); // may reject
render(stats);
} catch (err) {
console.error(err);
// Show a friendly message in the UI
showToast('Unable to load dashboard. Please try again later.');
// Send to monitoring
reportErrorToService(err);
}
}
The Three-Step Error Handling Pattern
The clean-code-javascript guide implicitly recommends a consistent three-step pattern for every catch block:
console.error– Makes the error visible in development and production debugging tools, preserving stack traces.- User notification – Surfaces the failure in the UI so users know something went wrong rather than experiencing silent freezes.
- Service reporting – Sends context to external monitoring (Sentry, Datadog, Rollbar) for aggregation and alerting.
You can DRY this pattern by creating a helper utility:
// utils/error.js
export const handleError = err => {
console.error(err);
notifyUserOfError(err);
reportErrorToService(err);
};
// usage
try {
riskyOperation();
} catch (e) {
handleError(e);
}
Architectural Benefits of Explicit Error Handling
Adopting these patterns from ryanmcdermott/clean-code-javascript provides several architectural advantages:
- Fail fast – Throwing errors forces the runtime to stop the current call stack, making the failure point obvious rather than allowing undefined values to propagate.
- Observable failures – By logging or reporting, you turn silent failures into observable events that can be acted upon immediately.
- Separation of concerns – Error handling code stays centralized in helpers or catch blocks, keeping the happy-path logic uncluttered and focused on its single responsibility.
Summary
- Never silently swallow errors in
try/catchblocks or Promise rejections according to theclean-code-javascriptguidelines. - Use
console.errorinstead ofconsole.logto preserve stack traces and visibility. - Implement the three-step pattern: log to console, notify the user, and report to a monitoring service.
- Apply consistent handling to both synchronous and asynchronous code using
.catch()orasync/awaitwithtry/catch. - Centralize error handling logic in helper utilities to keep your codebase DRY and maintainable.
Frequently Asked Questions
What is the clean-code-javascript recommendation for handling errors?
The ryanmcdermott/clean-code-javascript repository recommends treating error handling as a first-class concern by never silently swallowing errors and always making them visible. According to README.md lines 2003–2076, you should handle caught errors from try/catch blocks and rejected Promises by logging to console.error, notifying the user, and reporting to external monitoring services.
Should I use console.log or console.error for errors in JavaScript?
You should use console.error for errors instead of console.log. The clean-code-javascript guide emphasizes that console.error provides better visibility in browser DevTools and Node.js environments, preserves stack traces more reliably, and allows filtering in production logging pipelines. Using console.log for errors makes debugging harder because it treats critical failures the same as informational messages.
How should I handle errors in async/await functions?
Handle errors in async/await functions by wrapping awaited calls in try/catch blocks, applying the same three-step pattern used for synchronous errors. According to the source analysis of README.md lines 2045–2048, you should catch the error, log it with console.error, notify the user if appropriate, and report it to a monitoring service. Never let Promise rejections bubble up unhandled, as this creates silent failures in asynchronous flows.
Is it ever okay to silently catch and ignore an error?
No, it is never okay to silently catch and ignore an error according to the clean-code-javascript guidelines. Doing so eliminates your ability to debug the issue, prevents users from knowing something went wrong, and allows corrupted state to propagate through your application. If you truly must suppress an error, you should still log it with console.error and include a comment explaining why the error is being ignored for this specific edge case.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →