Error Handling and Robust Control Flow
Learn how JavaScript errors propagate, how try/catch/finally interact with async code, how custom errors work, and how to design reliable error boundaries.
throw and try/catch
JavaScript uses exceptions for abrupt control flow. throw can throw any value, although throwing an Error object is strongly preferred because it provides a stack trace and standardized error metadata. try/catch intercepts exceptions thrown during execution of the protected block.
Catch blocks should add useful context or recover when appropriate rather than blindly swallowing failures.
function parseUser(input) {
try {
return JSON.parse(input);
} catch (error) {
throw new Error('Invalid user payload', { cause: error });
}
}
try {
parseUser('{invalid}');
} catch (error) {
console.error(error.message);
console.error(error.cause);
}- Prefer throwing Error objects.
- Use catch blocks to recover or add meaningful context.
- Error causes can preserve the original failure.
What is generally the best thing to throw for an application error?
finally and Cleanup
A finally block executes after the try/catch path regardless of whether the operation succeeds or throws, making it useful for cleanup. Be careful when returning from finally: a return there can override a return or exception from the try/catch block.
let connection;
try {
connection = openConnection();
useConnection(connection);
} catch (error) {
console.error(error);
} finally {
connection?.close();
}- finally is designed for cleanup.
- Cleanup should happen even when an exception occurs.
- Avoid returning from finally unless you intentionally want to override control flow.
When is finally useful?
Custom Error Classes
Custom error classes allow applications to distinguish categories of failures. Extending Error allows code to use instanceof or other properties to make decisions without parsing human-readable messages.
For example, a REST API might distinguish validation errors, authentication errors, and infrastructure errors.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
throw new ValidationError('Email is required', 'email');- Custom errors make error categories explicit.
- Structured error properties are better than parsing messages.
- Error classes can be checked using instanceof.
What is a major benefit of custom Error classes?