While catching errors is essential, sometimes you need to throw your own errors to signal that something went wrong. JavaScript's throw statement lets you create and throw errors intentionally, allowing you to validate inputs, enforce business rules, and communicate problems clearly. By creating custom error classes, you can build sophisticated error handling systems where different error types are handled differently. This lesson teaches you to throw errors like a pro and build robust, self-documenting error systems!
The throw Statement
Basic throw Syntax
// Throw stops execution and creates an error
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
// Execution stops here!
console.log('This never runs');
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Throws error!
console.log('This never runs');
} catch (error) {
console.log('Caught:', error.message);
}
// Output:
// 5
// Caught: Cannot divide by zero
// throw immediately stops execution and jumps to catchYou Can throw Any Value
// Throwing strings (not recommended)
try {
throw 'Something went wrong';
} catch (error) {
console.log(error); // "Something went wrong"
}
// Throwing numbers
try {
throw 404;
} catch (error) {
console.log('Error code:', error); // 404
}
// Throwing objects
try {
throw { code: 404, message: 'Not found' };
} catch (error) {
console.log(error.code, error.message);
}
// Best practice: Throw Error objects
try {
throw new Error('This is best practice');
} catch (error) {
console.log(error.message);
console.log(error.stack); // Has stack trace!
}
// Error objects provide more informationBest Practice: Always throw Error objects (or custom error classes that extend Error) because they automatically include stack traces for debugging.
The Error Constructor
// Creating Error objects
let error1 = new Error('Something went wrong');
console.log(error1.name); // "Error"
console.log(error1.message); // "Something went wrong"
console.log(error1.stack); // Stack trace
// You can throw it
throw error1;
// Or throw inline
throw new Error('Inline error');
// Error with detailed message
function validateAge(age) {
if (age < 0) {
throw new Error(`Invalid age: ${age}. Age cannot be negative.`);
}
if (age < 18) {
throw new Error(`Age ${age} is below minimum required age of 18.`);
}
return true;
}
try {
validateAge(-5);
} catch (error) {
console.log(error.message);
// Invalid age: -5. Age cannot be negative.
}Built-in Error Types
// Different built-in error types
// TypeError - Wrong type
function processString(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.toUpperCase();
}
// RangeError - Value out of range
function setAge(age) {
if (age < 0 || age > 150) {
throw new RangeError('Age must be between 0 and 150');
}
}
// ReferenceError - Invalid reference
// Usually thrown automatically, but can throw manually
function getVariable(name) {
if (!window[name]) {
throw new ReferenceError(`Variable '${name}' is not defined`);
}
return window[name];
}
// Using specific error types helps with debugging
try {
processString(123);
} catch (error) {
if (error instanceof TypeError) {
console.log('Type error:', error.message);
}
}Custom Error Classes
Creating Custom Errors
// Basic custom error class
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
// Using custom error
function validateEmail(email) {
if (!email) {
throw new ValidationError('Email is required');
}
if (!email.includes('@')) {
throw new ValidationError('Invalid email format');
}
return true;
}
// Catching custom errors
try {
validateEmail('invalid');
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation failed:', error.message);
} else {
console.log('Unknown error:', error.message);
}
}
// Output: Validation failed: Invalid email formatCustom Error with Additional Properties
// Enhanced custom error
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field; // Additional property
this.timestamp = new Date();
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email is required', 'email');
}
if (!user.password) {
throw new ValidationError('Password is required', 'password');
}
if (user.password.length < 8) {
throw new ValidationError('Password too short', 'password');
}
}
try {
validateUser({ email: 'user@test.com', password: 'short' });
} catch (error) {
console.log('Error:', error.message);
console.log('Field:', error.field);
console.log('Time:', error.timestamp);
}
// Output:
// Error: Password too short
// Field: password
// Time: 2024-01-01T12:00:00.000ZMultiple Custom Error Types
// Define multiple error types
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = 'DatabaseError';
this.query = query;
}
}
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.name = 'AuthenticationError';
}
}
// Using different error types
function processRequest(data) {
// Validation
if (!data.userId) {
throw new ValidationError('User ID is required');
}
// Authentication
if (!data.token) {
throw new AuthenticationError('No authentication token');
}
// Network simulation
if (!navigator.onLine) {
throw new NetworkError('No internet connection', 0);
}
// Database simulation
if (data.userId < 0) {
throw new DatabaseError('Invalid user ID', `SELECT * FROM users WHERE id = ${data.userId}`);
}
}
// Handle different errors differently
try {
processRequest({ userId: -1, token: 'abc' });
} catch (error) {
if (error instanceof ValidationError) {
showFormError(error.message);
} else if (error instanceof AuthenticationError) {
redirectToLogin();
} else if (error instanceof NetworkError) {
showOfflineMessage();
} else if (error instanceof DatabaseError) {
logDatabaseError(error.query);
showServerError();
} else {
showGenericError();
}
}Input Validation Pattern
// Comprehensive validation with custom errors
class ValidationError extends Error {
constructor(message, field, value) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
}
}
function validateUserRegistration(data) {
let errors = [];
// Username validation
if (!data.username) {
errors.push(new ValidationError('Username is required', 'username', data.username));
} else if (data.username.length < 3) {
errors.push(new ValidationError('Username too short (min 3)', 'username', data.username));
} else if (data.username.length > 20) {
errors.push(new ValidationError('Username too long (max 20)', 'username', data.username));
}
// Email validation
if (!data.email) {
errors.push(new ValidationError('Email is required', 'email', data.email));
} else if (!data.email.includes('@')) {
errors.push(new ValidationError('Invalid email format', 'email', data.email));
}
// Password validation
if (!data.password) {
errors.push(new ValidationError('Password is required', 'password', null));
} else if (data.password.length < 8) {
errors.push(new ValidationError('Password too short (min 8)', 'password', null));
}
// Age validation
if (data.age !== undefined) {
if (typeof data.age !== 'number') {
errors.push(new ValidationError('Age must be a number', 'age', data.age));
} else if (data.age < 0) {
errors.push(new ValidationError('Age cannot be negative', 'age', data.age));
} else if (data.age < 18) {
errors.push(new ValidationError('Must be 18 or older', 'age', data.age));
}
}
// If any errors, throw them
if (errors.length > 0) {
throw errors;
}
return true;
}
// Usage
try {
validateUserRegistration({
username: 'ab',
email: 'invalid',
password: 'short',
age: 15
});
console.log('Validation passed');
} catch (errors) {
console.log('Validation failed:');
errors.forEach(error => {
console.log(` - ${error.field}: ${error.message}`);
});
}
// Output:
// Validation failed:
// - username: Username too short (min 3)
// - email: Invalid email format
// - password: Password too short (min 8)
// - age: Must be 18 or olderError Hierarchies
// Base custom error
class AppError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
this.timestamp = new Date();
}
}
// Specific error types extending base
class ClientError extends AppError {
constructor(message, statusCode = 400) {
super(message);
this.statusCode = statusCode;
}
}
class ServerError extends AppError {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
}
}
// More specific errors
class ValidationError extends ClientError {
constructor(message, field) {
super(message, 400);
this.field = field;
}
}
class AuthenticationError extends ClientError {
constructor(message) {
super(message, 401);
}
}
class AuthorizationError extends ClientError {
constructor(message) {
super(message, 403);
}
}
class NotFoundError extends ClientError {
constructor(resource) {
super(`${resource} not found`, 404);
this.resource = resource;
}
}
class DatabaseError extends ServerError {
constructor(message, query) {
super(message, 500);
this.query = query;
}
}
// Usage with hierarchy
function handleRequest(request) {
try {
if (!request.user) {
throw new AuthenticationError('Not authenticated');
}
if (!request.user.isAdmin) {
throw new AuthorizationError('Admin access required');
}
let resource = findResource(request.resourceId);
if (!resource) {
throw new NotFoundError('Resource');
}
return processResource(resource);
} catch (error) {
if (error instanceof ClientError) {
sendClientError(error.statusCode, error.message);
} else if (error instanceof ServerError) {
logServerError(error);
sendServerError(error.statusCode);
} else {
sendGenericError();
}
}
}Practical Patterns
Pattern 1: Guard Clauses
// Validate early, throw early
function processPayment(amount, cardNumber, cvv) {
// Guard clause 1: Amount
if (amount <= 0) {
throw new Error('Amount must be positive');
}
// Guard clause 2: Card number
if (!cardNumber || cardNumber.length !== 16) {
throw new Error('Invalid card number');
}
// Guard clause 3: CVV
if (!cvv || cvv.length !== 3) {
throw new Error('Invalid CVV');
}
// All validations passed, process payment
return chargeCard(amount, cardNumber, cvv);
}
// Clear, readable, fails fastPattern 2: Assertion Functions
// Assertion helper
function assert(condition, message) {
if (!condition) {
throw new Error(`Assertion failed: ${message}`);
}
}
function calculateDiscount(price, percentage) {
assert(price > 0, 'Price must be positive');
assert(percentage >= 0 && percentage <= 100, 'Percentage must be 0-100');
return price * (percentage / 100);
}
// Type assertion
function assertType(value, type, name) {
if (typeof value !== type) {
throw new TypeError(`${name} must be a ${type}`);
}
}
function greet(name, age) {
assertType(name, 'string', 'Name');
assertType(age, 'number', 'Age');
console.log(`Hello ${name}, you are ${age} years old`);
}Pattern 3: Error Factory
// Error factory for consistent error creation
const ErrorFactory = {
validation: (field, message) => {
let error = new Error(message);
error.name = 'ValidationError';
error.field = field;
return error;
},
notFound: (resource) => {
let error = new Error(`${resource} not found`);
error.name = 'NotFoundError';
error.resource = resource;
error.statusCode = 404;
return error;
},
unauthorized: (message = 'Unauthorized') => {
let error = new Error(message);
error.name = 'UnauthorizedError';
error.statusCode = 401;
return error;
},
server: (message) => {
let error = new Error(message);
error.name = 'ServerError';
error.statusCode = 500;
return error;
}
};
// Usage
function getUser(userId) {
if (!userId) {
throw ErrorFactory.validation('userId', 'User ID is required');
}
let user = database.findUser(userId);
if (!user) {
throw ErrorFactory.notFound('User');
}
return user;
}Practical Examples
Example 1: API Error Handler
// Complete API error system
class APIError extends Error {
constructor(message, statusCode, details) {
super(message);
this.name = 'APIError';
this.statusCode = statusCode;
this.details = details;
this.timestamp = new Date().toISOString();
}
toJSON() {
return {
error: this.name,
message: this.message,
statusCode: this.statusCode,
details: this.details,
timestamp: this.timestamp
};
}
}
async function fetchUserData(userId) {
try {
// Validation
if (!userId) {
throw new APIError('User ID is required', 400, { field: 'userId' });
}
// Make request
let response = await fetch(`/api/users/${userId}`);
// Handle HTTP errors
if (response.status === 404) {
throw new APIError('User not found', 404, { userId });
}
if (response.status === 401) {
throw new APIError('Authentication required', 401);
}
if (response.status >= 500) {
throw new APIError('Server error', response.status, {
endpoint: `/api/users/${userId}`
});
}
if (!response.ok) {
throw new APIError('Request failed', response.status);
}
// Parse response
let data = await response.json();
return data;
} catch (error) {
if (error instanceof APIError) {
console.error('API Error:', error.toJSON());
throw error;
} else {
// Network error or parsing error
let apiError = new APIError('Network error', 0, { originalError: error.message });
console.error('Network Error:', apiError.toJSON());
throw apiError;
}
}
}
// Usage
try {
let user = await fetchUserData(123);
console.log('User loaded:', user);
} catch (error) {
if (error.statusCode === 404) {
showMessage('User not found');
} else if (error.statusCode === 401) {
redirectToLogin();
} else if (error.statusCode >= 500) {
showMessage('Server error. Please try again later.');
} else if (error.statusCode === 0) {
showMessage('Network error. Check your connection.');
}
}Example 2: Form Validation System
// Comprehensive form validation
class FormValidationError extends Error {
constructor(errors) {
super('Form validation failed');
this.name = 'FormValidationError';
this.errors = errors; // Array of field errors
}
hasError(field) {
return this.errors.some(e => e.field === field);
}
getError(field) {
return this.errors.find(e => e.field === field);
}
getAllErrors() {
return this.errors.map(e => `${e.field}: ${e.message}`);
}
}
class Validator {
constructor() {
this.errors = [];
}
required(value, field) {
if (!value) {
this.errors.push({ field, message: 'This field is required' });
}
return this;
}
email(value, field) {
if (value && !value.includes('@')) {
this.errors.push({ field, message: 'Invalid email format' });
}
return this;
}
minLength(value, length, field) {
if (value && value.length < length) {
this.errors.push({ field, message: `Minimum ${length} characters required` });
}
return this;
}
maxLength(value, length, field) {
if (value && value.length > length) {
this.errors.push({ field, message: `Maximum ${length} characters allowed` });
}
return this;
}
pattern(value, regex, field, message) {
if (value && !regex.test(value)) {
this.errors.push({ field, message });
}
return this;
}
validate() {
if (this.errors.length > 0) {
throw new FormValidationError(this.errors);
}
}
}
// Usage
function validateRegistrationForm(formData) {
let validator = new Validator();
validator
.required(formData.username, 'username')
.minLength(formData.username, 3, 'username')
.maxLength(formData.username, 20, 'username')
.required(formData.email, 'email')
.email(formData.email, 'email')
.required(formData.password, 'password')
.minLength(formData.password, 8, 'password')
.pattern(
formData.password,
/[A-Z]/,
'password',
'Must contain at least one uppercase letter'
)
.pattern(
formData.password,
/[0-9]/,
'password',
'Must contain at least one number'
)
.validate();
}
// Using the validator
try {
validateRegistrationForm({
username: 'ab',
email: 'invalid',
password: 'short'
});
console.log('Form valid - submit!');
submitForm();
} catch (error) {
if (error instanceof FormValidationError) {
console.log('Validation errors:');
error.getAllErrors().forEach(err => console.log(' -', err));
// Display errors in UI
displayFormErrors(error.errors);
}
}
// Output:
// Validation errors:
// - username: Minimum 3 characters required
// - email: Invalid email format
// - password: Minimum 8 characters required
// - password: Must contain at least one uppercase letter
// - password: Must contain at least one numberCustom Error Mastery
Create and throw custom errors
console.log() to see your output in the console above.Key Takeaways
throwstops execution and creates an error- Can throw any value, but Error objects are best practice
- Error objects include stack traces for debugging
- Create custom error classes by extending Error
- Custom errors can have additional properties (field, statusCode, etc.)
- Use
instanceofto check error types - Different error types enable different handling strategies
- Guard clauses validate early and throw early
- Error hierarchies organize related error types
- Always provide clear, actionable error messages
What's Next?
Congratulations! You've completed the Error Handling section and now know how to throw custom errors, create error classes, and build sophisticated error handling systems. You can validate inputs, enforce business rules, and handle different error types appropriately.
You've now completed 49 out of 62 JavaScript tutorial pages (79% complete)! You've mastered 11 major sections. You're getting very close to completing the entire JavaScript tutorial series!
💪 Practice Challenge:
Before moving on, try creating:
- A custom ValidationError class with field and value properties
- A function that throws errors for invalid inputs
- Multiple custom error types (Network, Auth, Database)
- A form validator that throws arrays of validation errors
- An error handling system that handles different types differently