Unhandled errors in JavaScript either crash a script or silently swallow a rejected Promise — proper error handling is what stands between a graceful failure and a broken page.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
function validateAge(age) {
if (age < 0) throw new ValidationError('Age cannot be negative', 'age');
return age;
}
try {
validateAge(-5);
} catch (err) {
if (err instanceof ValidationError) {
console.error(<code>Invalid ${err.field}: ${err.message}</code>);
} else {
throw err; // re-throw anything we don't specifically handle
}
} finally {
console.log('Validation attempt complete');
}
// Async errors need try/catch around await, OR a .catch() on the promise
async function loadUser(id) {
try {
const res = await fetch(<code>/api/users/${id}</code>);
return await res.json();
} catch (err) {
console.error('Failed to load user:', err);
return null;
}
}
Custom error classes let calling code distinguish between error types with instanceof instead of parsing error message strings — genuinely important once an application has more than one or two kinds of failure to handle differently.