JavaScript Error Objects
Introduction
When you're writing JavaScript code, things don't always go as planned. Data might be in an unexpected format, network requests can fail, or you might have made a mistake in your code. JavaScript provides Error objects as a structured way to handle these situations.
Error objects are a fundamental part of JavaScript's error handling mechanism. They encapsulate information about what went wrong, where it happened, and sometimes even why. Understanding how to work with these objects is essential for writing robust, maintainable code that can gracefully handle unexpected situations.
What are Error Objects?
An Error object in JavaScript is an instance of the Error
constructor. When something goes wrong in your code, JavaScript creates one of these objects containing information about the error that occurred.
Here's the basic syntax for creating an Error object:
const error = new Error(message);
The message
parameter is a human-readable description of the error. It's optional, but it's good practice to include a meaningful message.
Properties of Error Objects
Every Error object has the following standard properties:
1. message
The message
property contains a description of the error:
const error = new Error("Something went wrong!");
console.log(error.message); // Output: "Something went wrong!"
2. name
The name
property specifies the type of error:
const error = new Error("Database connection failed");
console.log(error.name); // Output: "Error"
For the base Error
constructor, the name
property is set to "Error". For other error types (which we'll explore shortly), the name
property will reflect the specific error type.
3. stack
The stack
property contains a stack trace - a textual representation of the call stack at the point where the error was created:
function firstFunction() {
secondFunction();
}
function secondFunction() {
thirdFunction();
}
function thirdFunction() {
const error = new Error("An error occurred!");
console.log(error.stack);
}
firstFunction();
// Output might look like:
// Error: An error occurred!
// at thirdFunction (file.js:10:17)
// at secondFunction (file.js:6:3)
// at firstFunction (file.js:2:3)
// at file.js:14:1
The stack trace is incredibly useful for debugging as it shows the path of execution that led to the error.
Built-in Error Types
JavaScript has several built-in error types that extend the base Error
object. Each specializes in representing a different category of error:
1. SyntaxError
Occurs when there's an error in the syntax of your code:
try {
eval("console.log('Hello world'"); // Missing closing parenthesis
} catch (error) {
console.log(error.name); // Output: "SyntaxError"
console.log(error.message); // Output will describe the syntax issue
}
2. ReferenceError
Occurs when you try to reference a variable that doesn't exist:
try {
console.log(undefinedVariable); // This variable doesn't exist
} catch (error) {
console.log(error.name); // Output: "ReferenceError"
console.log(error.message); // Output: "undefinedVariable is not defined"
}
3. TypeError
Occurs when a value is not of the expected type:
try {
const num = 123;
num.toUpperCase(); // Numbers don't have this method
} catch (error) {
console.log(error.name); // Output: "TypeError"
console.log(error.message); // Output: "num.toUpperCase is not a function"
}
4. RangeError
Occurs when a value is outside the allowable range:
try {
const arr = new Array(-1); // Array size can't be negative
} catch (error) {
console.log(error.name); // Output: "RangeError"
console.log(error.message); // Output will describe the range issue
}
5. URIError
Occurs when using global URI handling functions incorrectly:
try {
decodeURI('%'); // % should be followed by two hex digits
} catch (error) {
console.log(error.name); // Output: "URIError"
console.log(error.message); // Output will describe the URI issue
}
6. EvalError
Historically used for errors in the global eval()
function, but in modern JavaScript, SyntaxError
, ReferenceError
, or TypeError
are thrown instead.
Creating Custom Error Types
Sometimes the built-in error types don't fully capture the nature of an error in your application. In these cases, you can create custom error types by extending the Error
class:
class DatabaseError extends Error {
constructor(message, code) {
super(message);
this.name = "DatabaseError";
this.code = code;
}
}
try {
throw new DatabaseError("Failed to connect to database", "DB_CONN_ERROR");
} catch (error) {
console.log(error.name); // Output: "DatabaseError"
console.log(error.message); // Output: "Failed to connect to database"
console.log(error.code); // Output: "DB_CONN_ERROR"
}
Custom error types allow you to add application-specific properties and methods to your errors, making them more descriptive and useful.
Practical Error Handling Examples
Let's look at some practical examples of using Error objects in real-world scenarios.
Example 1: Form Validation
function validateUsername(username) {
if (!username) {
throw new Error("Username cannot be empty");
}
if (username.length < 3) {
throw new Error("Username must be at least 3 characters long");
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
throw new Error("Username can only contain letters, numbers, and underscores");
}
return true;
}
try {
validateUsername("user@name");
} catch (error) {
console.log(error.message); // Output: "Username can only contain letters, numbers, and underscores"
// Display error message to the user in the UI
}
Example 2: API Requests with Custom Errors
class ApiError extends Error {
constructor(message, status) {
super(message);
this.name = "ApiError";
this.status = status;
}
}
async function fetchUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new ApiError(`Failed to fetch user data: ${response.statusText}`, response.status);
}
return await response.json();
} catch (error) {
if (error instanceof ApiError) {
console.error(`API Error (${error.status}): ${error.message}`);
// Handle specific status codes
if (error.status === 404) {
// Handle not found
} else if (error.status === 401) {
// Handle unauthorized
}
} else {
console.error("Network or other error:", error.message);
}
throw error; // Rethrow to let callers handle it
}
}
// Using the function
fetchUserData(123)
.then(data => {
// Process user data
})
.catch(error => {
// Handle error at a higher level
});
Example 3: Error Handling in a Node.js Application
const fs = require('fs').promises;
class FileSystemError extends Error {
constructor(message, operation, filePath) {
super(message);
this.name = "FileSystemError";
this.operation = operation;
this.filePath = filePath;
}
}
async function readConfigFile(path) {
try {
const data = await fs.readFile(path, 'utf8');
return JSON.parse(data);
} catch (error) {
if (error.code === 'ENOENT') {
throw new FileSystemError(
`Config file not found: ${path}`,
'read',
path
);
} else if (error instanceof SyntaxError) {
throw new FileSystemError(
`Invalid JSON in config file: ${error.message}`,
'parse',
path
);
}
throw error;
}
}
// Usage
readConfigFile('config.json')
.then(config => {
// Use the configuration
})
.catch(error => {
if (error instanceof FileSystemError) {
console.error(`${error.name}: ${error.message}`);
console.error(`Operation: ${error.operation}, Path: ${error.filePath}`);
// Specific handling based on the operation
if (error.operation === 'read') {
// Create a default config file
} else if (error.operation === 'parse') {
// Alert admin about corrupted config
}
} else {
console.error('Unexpected error:', error);
}
});
Summary
JavaScript Error objects are a powerful mechanism for handling and communicating errors in your code. In this guide, we've explored:
- The basic structure of Error objects and their properties (
message
,name
,stack
) - Built-in error types like
SyntaxError
,ReferenceError
, andTypeError
- How to create custom error types for application-specific errors
- Practical examples of error handling in different contexts
Effective use of Error objects leads to more robust, maintainable code that can gracefully handle unexpected situations. By creating descriptive error messages and leveraging the stack trace, you can make debugging easier. Custom error types allow you to add context-specific information that can help diagnose and recover from errors.
Additional Resources
Exercises
- Create a custom
ValidationError
class that accepts a field name and a validation rule that failed. - Write a function that reads data from localStorage and handles different potential errors (item not found, storage quota exceeded, etc.).
- Implement a "retry" mechanism that catches specific errors and retries an operation a limited number of times before giving up.
- Create a logging utility that formats and logs different types of errors in a consistent way.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)