Why Ignoring Exceptions Is Killing Your JavaScript Code (And How to Fix It)
This article explains why proper exception handling is essential for high‑quality JavaScript, contrasts using return codes with real exceptions, shows clean versus dirty code examples, and offers practical guidelines such as defining custom exception hierarchies, handling Promise rejections, and providing contextual error information.
Source: Clean Code Applied to JavaScript — Part V. Exceptions
In software development, exception handling is an indispensable part of high‑quality code, allowing us to control unexpected situations and unimplemented logic effectively.
Exceptions should be used for uncontrolled or undeveloped cases, not as a substitute for business logic with return statements.
This article provides several recommendations for exception handling to improve code quality.
Use Exceptions Instead of Return Codes
If a programming language supports exceptions, it is advisable to use them. Some languages lack exception support, and some developers are unaware of the benefits, leading them to rely on return‑code checks, which are more verbose and error‑prone.
The first code snippet checks return values with an if statement, forcing callers to perform immediate error checks, increasing complexity and risk of omission. The second snippet isolates business logic using exceptions, offering three advantages:
Separates business logic from error handling, treating them as distinct concerns.
Reduces code redundancy and improves readability.
Delegates exception responsibility to the language runtime.
// Dirty
class Laptop {
sendShutDown() {
const deviceID = getID(DEVICE_LAPTOP);
if (deviceID !== DEVICE_STATUS.INVALID) {
const laptop = DB.findOne(deviceID);
if (laptop.getStatus() !== DEVICE_SUSPENDED) {
pauseDevice(deviceID);
clearDeviceWorkQueue(deviceID);
closeDevice(deviceID);
} else {
logger.log('Device suspended. Unable to shut down');
}
} else {
logger.log('Invalid handle for: ' + DEVICE_LAPTOP.toString());
}
}
} // Clean
class Laptop {
sendShutDown() {
try {
tryToShutDown();
} catch (error) {
logger.log(error);
}
}
tryToShutDown() {
const deviceID = getID(DEVICE_LAPTOP);
const laptop = DB.findOne(deviceID);
pauseDevice(deviceID);
clearDeviceWorkQueue(deviceID);
closeDevice(deviceID);
}
getID(deviceID) {
...
throw new DeviceShutDownError('Invalid handle for: ' + deviceID.toString());
...
}
}Don’t Ignore Exception Handling
Don’t act like an ostrich burying its head; ignoring caught errors is meaningless.
Using console.log or system.out.println as error handling is equivalent to doing nothing. Unhandled exceptions hide problems that could reveal hard‑to‑find bugs.
Translator note: try ... catch lets JavaScript continue execution and avoid white‑screen crashes, but swallowing caught errors is dangerous. Typically, error‑reporting tools (e.g., Sentry) capture uncaught errors; if you catch them without reporting, the issue may go unnoticed. Even simple logging is better than nothing.
The first example shows a novice ignoring exceptions; the second demonstrates proper handling despite extra effort.
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
} try {
functionThatMightThrow();
} catch (error) {
console.error(error);
notifyUserOfError(error);
reportErrorToService(error);
}Don’t Ignore Promise Rejection
In JavaScript, pay special attention to Promise rejections, which also need proper exception handling.
The following example mirrors the previous one but uses a Promise.
getData()
.then(data => functionThatMightThrow(data))
.catch(error => console.log); getData()
.then(data => functionThatMightThrow(data))
.catch(error => {
console.log(error);
notifyUserOfError(error);
reportErrorToService(error);
});Define an Exceptions Hierarchy
Languages provide base exceptions like NullPointerException or ArrayIndexOutOfBoundsException, which are unrelated to business logic. Custom exception hierarchies should represent domain‑specific error conditions.
Below, two custom exceptions— UserException and AdminException —are defined, illustrating how to model business‑related errors.
More specific exceptions such as UserRepeatException or UserNotFoundException improve semantic clarity.
export class UserException extends Error {
constructor(message) {
super(`User: ${message}`);
}
}
export class AdminException extends Error {
constructor(message) {
super(`Admin: ${message}`);
}
}
// Client code
const id = 1;
const user = this.users.find({ id });
if (user) {
throw new UserException('This user already exists');
}Provide Context for Exceptions
When an exception occurs, stack traces can be hard to analyze; adding concise contextual information helps. Such details should not be exposed to end users but can aid developers during debugging.
If you define an exception hierarchy, also attach meaningful context to each exception.
Summary
This article presents several recommendations for exception handling, a fundamental aspect of high‑quality software development that is often overlooked or misused.
Whenever a language offers exceptions, use them to focus on business logic rather than error‑driven control flow.
Key takeaways:
Use exceptions instead of return codes.
Don’t ignore exception handling.
Don’t ignore Promise rejections.
Define a clear exceptions hierarchy.
Provide contextual information with exceptions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
