JavaScript Error Handling
Learn how JavaScript handles errors using try, catch, finally, throw, Error objects, custom errors, error types, validation, debugging, asynchronous errors, and real-world error handling patterns.
Introduction
Programs do not always run exactly as expected. Users can enter invalid data, variables can be missing, JSON can contain invalid syntax, network requests can fail, browser storage can become unavailable, and application logic can encounter unexpected conditions.
Without proper error handling, one unexpected problem can stop the execution of important JavaScript code and leave the user with a broken interface.
JavaScript provides several tools for detecting, creating, catching, understanding, and recovering from errors. The most important are try, catch, finally, throw, Error objects, custom error classes, and Promise rejection handling.
- What errors are.
- Why error handling matters.
- How JavaScript handles errors.
- The difference between syntax, runtime, and logical errors.
- How the normal error flow works.
- How to use try.
- How to use catch.
- How try...catch works.
- How execution changes after an error.
- How to inspect Error objects.
- How to use error.name.
- How to use error.message.
- How to use error.stack.
- How optional catch binding works.
- How to use finally.
- When finally should be used.
- How to use throw.
- Why Error objects should usually be thrown.
- How the Error constructor works.
- The major built-in error types.
- How to identify different error types.
- How to create custom errors.
- How to create custom error classes.
- How to use error causes.
- How to re-throw errors.
- How nested error handling works.
- How functions communicate failures.
- How to validate input.
- How to handle form errors.
- How to handle JSON parsing errors.
- How to handle DOM errors.
- How to handle browser storage errors.
- How to handle network errors.
- How to distinguish network and HTTP errors.
- How Promise errors work.
- How to use Promise catch().
- How to handle async and await errors.
- How to handle multiple asynchronous operations.
- How global error handling works.
- How unhandled Promise rejections work.
- How to log errors.
- How to create user-friendly messages.
- How developer errors differ from user-facing errors.
- How applications recover from failures.
- How to use fallback values.
- How to implement retry logic.
- How to build a complete error-handling application.
What is an Error?
An error is a problem that prevents a program from executing normally or producing the expected result.
console.log(
username
);The variable username does not exist. JavaScript cannot continue executing that statement normally, so it creates and throws an error.
Why Error Handling Matters
Prevent Crashes
Handle expected failures before they break important application functionality.
Better Messages
Show useful messages instead of confusing technical errors.
Easier Debugging
Capture error names, messages, stack traces, and useful debugging information.
Recovery
Use fallback values, retries, or alternative application flows when something fails.
Validation
Detect invalid input and stop incorrect data from entering application logic.
Reliable Applications
Build software that behaves predictably even when unexpected conditions occur.
Real-World Analogy
Imagine driving on a road. Normally, you follow the planned route. If the road is blocked, you need a system that detects the problem and chooses another route instead of simply stopping forever.
NORMAL ROUTE
Start
│
▼
Drive Forward
│
▼
Destination
ERROR CONDITION
Start
│
▼
Road Blocked
│
▼
Detect Problem
│
▼
Alternative Route
│
▼
Continue Journey
JAVASCRIPT
try
│
▼
Error Occurs
│
▼
catch
│
▼
Handle Error
│
▼
Continue ProgramHow JavaScript Handles Errors
JAVASCRIPT EXECUTION
│
▼
Execute Statement
│
▼
Problem Occurs?
┌────┴────┐
▼ ▼
No Yes
│ │
▼ ▼
Continue Create Error
│
▼
Throw Error
│
▼
Is Error Caught?
┌───┴───┐
▼ ▼
Yes No
│ │
▼ ▼
Handle Execution
Error Stops in
│ Current Flow
▼
ContinueTypes of Errors
Programming errors are commonly grouped into syntax errors, runtime errors, and logical errors.
PROGRAMMING ERRORS
├── Syntax Errors
│
├── Runtime Errors
│
└── Logical ErrorsSyntax Errors
A syntax error occurs when JavaScript code does not follow the grammar rules of the language.
if (true {
console.log(
'Hello'
);
}- Syntax errors can prevent code from being parsed.
- The affected script may fail before normal execution begins.
- Common causes include missing brackets, parentheses, commas, and quotation marks.
Runtime Errors
A runtime error occurs while JavaScript is executing.
const user = null;
console.log(
user.name
);Logical Errors
A logical error occurs when the program runs without throwing an error but produces the wrong result.
function calculateArea(
width,
height
) {
return width + height;
}
console.log(
calculateArea(
10,
5
)
);The code executes successfully, but the formula is wrong. The correct calculation should multiply width by height.
Error Flow
console.log(
'Step 1'
);
console.log(
missingVariable
);
console.log(
'Step 3'
);The third statement does not run because the unhandled error interrupts the current execution flow.
The try Statement
The try block contains code that may throw an error.
try {
// Code that may fail
}A try block cannot be used alone. It must be followed by catch, finally, or both.
The catch Statement
The catch block runs when an error is thrown inside the associated try block.
try {
// Risky code
} catch (error) {
// Handle error
}try...catch
try {
console.log(
missingVariable
);
} catch (error) {
console.log(
'An error occurred'
);
}
console.log(
'Program continues'
);Execution Flow
ENTER try
│
▼
Execute Code
│
▼
Error Occurs?
┌──┴──┐
▼ ▼
No Yes
│ │
▼ ▼
Skip Stop Remaining
catch try Code
│ │
│ ▼
│ Enter catch
│ │
└──┬───┘
▼
Continue ProgramCode After an Error
try {
console.log(
'Before error'
);
console.log(
unknownValue
);
console.log(
'After error'
);
} catch (error) {
console.log(
'Error caught'
);
}After an error is thrown, the remaining statements in that try block are skipped.
The Error Object
The catch parameter normally receives an Error object containing information about the failure.
try {
console.log(
unknownVariable
);
} catch (error) {
console.log(error);
console.log(
typeof error
);
}Error name
try {
console.log(
missingVariable
);
} catch (error) {
console.log(
error.name
);
}Error message
try {
console.log(
missingVariable
);
} catch (error) {
console.log(
error.message
);
}Error stack
The stack property provides debugging information about where an error occurred and the chain of function calls that led to it.
try {
throw new Error(
'Something failed'
);
} catch (error) {
console.log(
error.stack
);
}- name identifies the error category.
- message describes the problem.
- stack helps developers trace where the problem occurred.
Optional Catch Binding
If the error object is not needed, the catch parameter can be omitted.
try {
JSON.parse(
'invalid json'
);
} catch {
console.log(
'Invalid data'
);
}The finally Statement
The finally block runs after try and catch processing, whether an error occurs or not.
try {
console.log(
'Trying operation'
);
} catch (error) {
console.log(
'Operation failed'
);
} finally {
console.log(
'Operation finished'
);
}try...catch...finally
try {
console.log(
'Start'
);
throw new Error(
'Failed'
);
} catch (error) {
console.log(
error.message
);
} finally {
console.log(
'Cleanup'
);
}
console.log(
'Continue'
);finally Execution Flow
TRY
│
▼
Error?
┌┴┐
│ │
No Yes
│ │
│ ▼
│ CATCH
│ │
└──┤
▼
FINALLY
│
▼
CONTINUEWhen to Use finally
- Hide loading indicators.
- Release temporary resources.
- Reset interface state.
- Run cleanup logic.
- Close temporary operations.
- Restore buttons after an operation.
- Run code that must execute after success or failure.
The throw Statement
The throw statement allows developers to create errors intentionally when application rules are violated.
const age = 15;
if (age < 18) {
throw new Error(
'You must be at least 18'
);
}Throwing Strings
try {
throw 'Something failed';
} catch (error) {
console.log(error);
}- Strings do not provide standard Error properties.
- They do not provide a normal error name.
- They provide less useful debugging information.
- Prefer throwing Error objects.
Throwing Numbers
try {
throw 404;
} catch (error) {
console.log(error);
}Throwing Error Objects
try {
throw new Error(
'Unable to load course'
);
} catch (error) {
console.log(
error.name
);
console.log(
error.message
);
}Why Throw Error Objects?
Error Name
Error objects identify the category of the failure.
Message
They contain a clear description of what went wrong.
Stack Trace
They provide useful information about where the failure occurred.
Custom Types
Applications can create specialized error classes for different failures.
The Error Constructor
const error =
new Error(
'Something went wrong'
);
console.log(
error.name
);
console.log(
error.message
);Built-in Error Types
Error
├── ReferenceError
├── TypeError
├── SyntaxError
├── RangeError
├── URIError
└── AggregateErrorError
throw new Error(
'Operation failed'
);ReferenceError
A ReferenceError commonly occurs when code tries to access an identifier that does not exist.
console.log(
unknownVariable
);TypeError
A TypeError occurs when a value is used in an inappropriate way for its type.
const user = null;
console.log(
user.name
);SyntaxError
A SyntaxError represents invalid JavaScript or other syntax that a parser cannot understand.
try {
JSON.parse(
'{invalid}'
);
} catch (error) {
console.log(
error.name
);
}RangeError
A RangeError occurs when a value is outside an allowed range.
try {
const numbers =
new Array(-1);
} catch (error) {
console.log(
error.name
);
}URIError
try {
decodeURIComponent(
'%'
);
} catch (error) {
console.log(
error.name
);
}AggregateError
AggregateError represents multiple errors grouped into one error object. It is commonly encountered with operations such as Promise.any().
const error =
new AggregateError(
[
new Error(
'Server 1 failed'
),
new Error(
'Server 2 failed'
)
],
'All servers failed'
);
console.log(
error.message
);
console.log(
error.errors.length
);Comparing Error Types
Error
General application failure.
ReferenceError
Missing identifier or reference.
TypeError
Invalid operation for a value type.
SyntaxError
Invalid syntax.
RangeError
Value outside an allowed range.
URIError
Invalid URI processing.
AggregateError
Multiple errors grouped together.Checking Error Types
try {
const user = null;
console.log(
user.name
);
} catch (error) {
if (
error instanceof
TypeError
) {
console.log(
'A type error occurred'
);
} else {
console.log(
'Another error occurred'
);
}
}Creating Custom Errors
function withdraw(
balance,
amount
) {
if (amount <= 0) {
throw new Error(
'Amount must be positive'
);
}
if (amount > balance) {
throw new Error(
'Insufficient balance'
);
}
return balance - amount;
}Custom Error Classes
class AppError extends Error {
constructor(
message,
code
) {
super(message);
this.name =
'AppError';
this.code =
code;
}
}
throw new AppError(
'Course not available',
'COURSE_UNAVAILABLE'
);ValidationError
class ValidationError
extends Error {
constructor(
message,
field
) {
super(message);
this.name =
'ValidationError';
this.field =
field;
}
}
function validateAge(age) {
if (
!Number.isFinite(age)
) {
throw new ValidationError(
'Age must be a number',
'age'
);
}
if (age < 18) {
throw new ValidationError(
'Age must be at least 18',
'age'
);
}
}NotFoundError
class NotFoundError
extends Error {
constructor(
resource,
id
) {
super(
`${resource} with ID ${id} was not found`
);
this.name =
'NotFoundError';
this.resource =
resource;
this.id =
id;
}
}Error Cause
An error can preserve the original reason for a higher-level failure using the cause option.
try {
JSON.parse(
'invalid'
);
} catch (originalError) {
throw new Error(
'Unable to load settings',
{
cause:
originalError
}
);
}Re-throwing Errors
A catch block can handle known errors and throw unknown errors again.
try {
riskyOperation();
} catch (error) {
if (
error instanceof
ValidationError
) {
console.log(
error.message
);
} else {
throw error;
}
}Nested try...catch
try {
console.log(
'Outer operation'
);
try {
JSON.parse(
'invalid'
);
} catch (error) {
console.log(
'Inner error handled'
);
}
console.log(
'Outer operation continues'
);
} catch (error) {
console.log(
'Outer error handled'
);
}Function Error Handling
function divide(
firstNumber,
secondNumber
) {
if (secondNumber === 0) {
throw new Error(
'Cannot divide by zero'
);
}
return (
firstNumber /
secondNumber
);
}
try {
const result =
divide(
10,
0
);
console.log(result);
} catch (error) {
console.log(
error.message
);
}Input Validation
function createUser(
name,
age
) {
if (
typeof name !==
'string' ||
name.trim() === ''
) {
throw new Error(
'Name is required'
);
}
if (
!Number.isFinite(age)
) {
throw new Error(
'Age must be a number'
);
}
if (age < 0) {
throw new Error(
'Age cannot be negative'
);
}
return {
name:
name.trim(),
age: age
};
}Form Validation
function validateForm(
formData
) {
if (
formData.name.trim() === ''
) {
throw new ValidationError(
'Name is required',
'name'
);
}
if (
!formData.email.includes(
'@'
)
) {
throw new ValidationError(
'Enter a valid email',
'email'
);
}
if (
formData.password.length <
8
) {
throw new ValidationError(
'Password must contain at least 8 characters',
'password'
);
}
return true;
}JSON Parsing Errors
const json =
'{ invalid json }';
try {
const data =
JSON.parse(json);
console.log(data);
} catch (error) {
console.log(
'Unable to parse JSON'
);
console.log(
error.message
);
}Safe JSON Parsing
function safeParseJSON(
value,
fallback = null
) {
try {
return JSON.parse(
value
);
} catch (error) {
return fallback;
}
}
const settings =
safeParseJSON(
'invalid',
{}
);
console.log(settings);DOM Errors
function getRequiredElement(
selector
) {
const element =
document.querySelector(
selector
);
if (element === null) {
throw new Error(
`Element not found: ${selector}`
);
}
return element;
}
try {
const button =
getRequiredElement(
'#saveButton'
);
button.disabled = false;
} catch (error) {
console.error(
error.message
);
}Storage Errors
function saveData(
key,
value
) {
try {
localStorage.setItem(
key,
JSON.stringify(
value
)
);
return true;
} catch (error) {
console.error(
'Storage failed:',
error
);
return false;
}
}Network Errors
fetch(
'/api/courses'
)
.then(function (response) {
return response.json();
})
.then(function (data) {
console.log(data);
})
.catch(function (error) {
console.error(
'Network request failed:',
error
);
});HTTP Errors
fetch() does not automatically reject its Promise for every HTTP error status such as 404 or 500. The response status should be checked explicitly.
async function loadCourses() {
const response =
await fetch(
'/api/courses'
);
if (!response.ok) {
throw new Error(
`HTTP error: ${response.status}`
);
}
return response.json();
}Promise Errors
const promise =
new Promise(
function (
resolve,
reject
) {
reject(
new Error(
'Operation failed'
)
);
}
);Promise catch()
Promise.resolve()
.then(function () {
throw new Error(
'Processing failed'
);
})
.catch(function (error) {
console.log(
error.message
);
});async and await Errors
async function loadData() {
try {
const response =
await fetch(
'/api/data'
);
if (!response.ok) {
throw new Error(
`Request failed: ${response.status}`
);
}
const data =
await response.json();
console.log(data);
} catch (error) {
console.error(
error.message
);
}
}Multiple Async Operations
async function loadDashboard() {
try {
const [
userResponse,
courseResponse
] = await Promise.all([
fetch('/api/user'),
fetch('/api/courses')
]);
if (
!userResponse.ok ||
!courseResponse.ok
) {
throw new Error(
'Dashboard data failed'
);
}
const [
user,
courses
] = await Promise.all([
userResponse.json(),
courseResponse.json()
]);
return {
user,
courses
};
} catch (error) {
console.error(
'Dashboard error:',
error
);
return null;
}
}Global Error Handling
window.addEventListener(
'error',
function (event) {
console.error(
'Global error:',
event.error
);
}
);- Do not replace local error handling with one global handler.
- Handle errors close to the operation when recovery is possible.
- Use global handlers for logging unexpected failures.
- Avoid exposing technical error details directly to users.
Unhandled Promise Rejections
window.addEventListener(
'unhandledrejection',
function (event) {
console.error(
'Unhandled Promise rejection:',
event.reason
);
}
);Error Logging
function logError(
error,
context = {}
) {
console.error({
name:
error.name,
message:
error.message,
stack:
error.stack,
context:
context,
timestamp:
new Date()
.toISOString()
});
}Console Error Methods
console.log(
'General information'
);
console.warn(
'Warning message'
);
console.error(
'Error message'
);
console.trace(
'Execution trace'
);User-Friendly Messages
try {
await loadCourses();
} catch (error) {
console.error(
'Technical error:',
error
);
message.textContent =
'We could not load the courses. Please try again.';
}Developer vs User Errors
DEVELOPER INFORMATION
TypeError:
Cannot read properties of null
Stack Trace:
at loadCourse(...)
at renderPage(...)
USER MESSAGE
We could not load this course.
Please try again.
RULE
Detailed Technical Information
│
▼
Developer Logs
Clear Helpful Information
│
▼
User InterfaceError Recovery
Retry
Try a temporary operation again when failure may be recoverable.
Fallback
Use default data when optional information cannot be loaded.
Cached Data
Use previously available data when a new request fails.
Alternative Flow
Move the user to another safe part of the application.
Explain the Problem
Tell the user what happened and what they can do next.
Preserve Work
Avoid losing user input when a recoverable operation fails.
Fallback Values
function readSettings() {
try {
const storedValue =
localStorage.getItem(
'settings'
);
if (storedValue === null) {
return {
theme: 'light'
};
}
return JSON.parse(
storedValue
);
} catch (error) {
return {
theme: 'light'
};
}
}Retry Logic
async function retry(
operation,
attempts = 3
) {
let lastError;
for (
let attempt = 1;
attempt <= attempts;
attempt++
) {
try {
return await operation();
} catch (error) {
lastError = error;
console.warn(
`Attempt ${attempt} failed`
);
}
}
throw lastError;
}
retry(
function () {
return fetch(
'/api/courses'
).then(
function (response) {
if (!response.ok) {
throw new Error(
'Request failed'
);
}
return response.json();
}
);
}
)
.then(console.log)
.catch(console.error);Error Handling Patterns
VALIDATION
Input
│
▼
Validate
│
├── Invalid → Throw Error
│
└── Valid → Continue
RECOVERY
Operation
│
▼
Fails
│
▼
Catch Error
│
├── Retry
├── Fallback
└── User Message
CLEANUP
try
│
▼
Operation
│
▼
catch
│
▼
finally
│
▼
Cleanup
PROPAGATION
Low-Level Function
│
▼
Throws Error
│
▼
Higher-Level Function
│
▼
Handles or Re-throwsComplete Error Handling Example
The following example creates a course enrollment form that validates user input, uses custom error classes, safely stores draft data, simulates an asynchronous submission, handles expected and unexpected failures, displays user-friendly messages, and always restores the interface after the operation finishes.
<div class="error-app">
<header class="app-header">
<p class="eyebrow">
JavaScript Error Handling Project
</p>
<h2>
Course Enrollment
</h2>
<p>
Complete the form to test
validation, custom errors,
recovery, and cleanup.
</p>
</header>
<form
id="enrollmentForm"
novalidate
>
<div class="form-group">
<label for="name">
Full Name
</label>
<input
id="name"
type="text"
>
<small
id="nameError"
class="field-error"
></small>
</div>
<div class="form-group">
<label for="email">
Email Address
</label>
<input
id="email"
type="email"
>
<small
id="emailError"
class="field-error"
></small>
</div>
<div class="form-group">
<label for="course">
Course
</label>
<select id="course">
<option value="">
Select Course
</option>
<option value="HTML">
HTML
</option>
<option value="CSS">
CSS
</option>
<option value="JavaScript">
JavaScript
</option>
</select>
<small
id="courseError"
class="field-error"
></small>
</div>
<button
id="submitButton"
type="submit"
>
Enroll Now
</button>
</form>
<div
id="statusMessage"
class="status-message"
>
Complete the form to begin.
</div>
<section class="error-panel">
<h3>
Developer Error Log
</h3>
<pre id="errorLog">
No errors recorded.
</pre>
</section>
</div>.error-app {
max-width: 720px;
margin: 40px auto;
padding: 28px;
border-radius: 18px;
background: var(--bg-secondary);
}
.app-header {
margin-bottom: 24px;
}
.eyebrow {
margin-bottom: 8px;
color: var(--accent-light);
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 7px;
font-size: 0.85rem;
font-weight: 600;
}
.form-group input,
.form-group select {
width: 100%;
padding: 12px;
border: 1px solid
var(--border-color);
border-radius: 8px;
background: var(--bg-primary);
color: var(--text-primary);
font: inherit;
}
.input-error {
border-color:
var(--danger) !important;
}
.field-error {
display: block;
min-height: 18px;
margin-top: 6px;
color: var(--danger);
}
#submitButton {
width: 100%;
padding: 12px 18px;
border: none;
border-radius: 8px;
background: var(--accent);
color: white;
cursor: pointer;
font: inherit;
font-weight: 600;
}
#submitButton:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.status-message {
margin-top: 18px;
padding: 14px;
border-radius: 8px;
background: var(--bg-primary);
}
.error-panel {
margin-top: 20px;
padding: 18px;
border-radius: 12px;
background: var(--bg-primary);
}
#errorLog {
max-height: 300px;
margin-top: 12px;
padding: 14px;
overflow: auto;
border-radius: 8px;
background: var(--bg-secondary);
white-space: pre-wrap;
overflow-wrap: anywhere;
}class ValidationError
extends Error {
constructor(
message,
field
) {
super(message);
this.name =
'ValidationError';
this.field =
field;
}
}
class SubmissionError
extends Error {
constructor(
message,
code
) {
super(message);
this.name =
'SubmissionError';
this.code =
code;
}
}
const form =
document.getElementById(
'enrollmentForm'
);
const nameInput =
document.getElementById(
'name'
);
const emailInput =
document.getElementById(
'email'
);
const courseInput =
document.getElementById(
'course'
);
const submitButton =
document.getElementById(
'submitButton'
);
const statusMessage =
document.getElementById(
'statusMessage'
);
const errorLog =
document.getElementById(
'errorLog'
);
function clearErrors() {
const fields = [
'name',
'email',
'course'
];
fields.forEach(
function (field) {
const input =
document.getElementById(
field
);
const errorElement =
document.getElementById(
`${field}Error`
);
input.classList.remove(
'input-error'
);
errorElement.textContent =
'';
}
);
}
function showFieldError(
field,
message
) {
const input =
document.getElementById(
field
);
const errorElement =
document.getElementById(
`${field}Error`
);
input.classList.add(
'input-error'
);
errorElement.textContent =
message;
input.focus();
}
function validateData(data) {
if (
data.name.trim() === ''
) {
throw new ValidationError(
'Full name is required.',
'name'
);
}
if (
data.name.trim().length <
2
) {
throw new ValidationError(
'Name must contain at least 2 characters.',
'name'
);
}
if (
data.email.trim() === ''
) {
throw new ValidationError(
'Email address is required.',
'email'
);
}
if (
!data.email.includes('@')
) {
throw new ValidationError(
'Enter a valid email address.',
'email'
);
}
if (data.course === '') {
throw new ValidationError(
'Select a course.',
'course'
);
}
}
function saveDraft(data) {
try {
sessionStorage.setItem(
'enrollment:draft',
JSON.stringify(
data
)
);
} catch (error) {
logError(
error,
{
operation:
'saveDraft'
}
);
}
}
function loadDraft() {
try {
const storedValue =
sessionStorage.getItem(
'enrollment:draft'
);
if (storedValue === null) {
return null;
}
return JSON.parse(
storedValue
);
} catch (error) {
logError(
error,
{
operation:
'loadDraft'
}
);
return null;
}
}
function clearDraft() {
try {
sessionStorage.removeItem(
'enrollment:draft'
);
} catch (error) {
logError(
error,
{
operation:
'clearDraft'
}
);
}
}
function simulateSubmission(
data
) {
return new Promise(
function (
resolve,
reject
) {
setTimeout(
function () {
const randomValue =
Math.random();
if (
randomValue < 0.25
) {
reject(
new SubmissionError(
'The enrollment service is temporarily unavailable.',
'SERVICE_UNAVAILABLE'
)
);
return;
}
resolve({
success: true,
enrollmentId:
Date.now(),
course:
data.course
});
},
1200
);
}
);
}
function logError(
error,
context = {}
) {
const logData = {
name:
error.name,
message:
error.message,
code:
error.code ?? null,
context:
context,
timestamp:
new Date()
.toISOString()
};
console.error(
logData
);
errorLog.textContent =
JSON.stringify(
logData,
null,
2
);
}
function setLoading(
isLoading
) {
submitButton.disabled =
isLoading;
submitButton.textContent =
isLoading
? 'Submitting...'
: 'Enroll Now';
}
function getFormData() {
return {
name:
nameInput.value,
email:
emailInput.value,
course:
courseInput.value
};
}
function restoreDraft() {
const draft =
loadDraft();
if (draft === null) {
return;
}
nameInput.value =
draft.name ?? '';
emailInput.value =
draft.email ?? '';
courseInput.value =
draft.course ?? '';
statusMessage.textContent =
'Your temporary draft was restored.';
}
function handleInput() {
const data =
getFormData();
saveDraft(data);
}
async function handleSubmit(
event
) {
event.preventDefault();
clearErrors();
const data =
getFormData();
try {
validateData(data);
setLoading(true);
statusMessage.textContent =
'Submitting enrollment...';
const result =
await simulateSubmission(
data
);
clearDraft();
form.reset();
statusMessage.textContent =
`Enrollment successful. ID: ${result.enrollmentId}`;
errorLog.textContent =
'No errors recorded.';
} catch (error) {
if (
error instanceof
ValidationError
) {
showFieldError(
error.field,
error.message
);
statusMessage.textContent =
'Please correct the highlighted field.';
return;
}
if (
error instanceof
SubmissionError
) {
logError(
error,
{
operation:
'submitEnrollment'
}
);
statusMessage.textContent =
'We could not complete your enrollment. Please try again.';
return;
}
logError(
error,
{
operation:
'unexpected'
}
);
statusMessage.textContent =
'Something unexpected happened. Please try again.';
} finally {
setLoading(false);
}
}
nameInput.addEventListener(
'input',
handleInput
);
emailInput.addEventListener(
'input',
handleInput
);
courseInput.addEventListener(
'change',
handleInput
);
form.addEventListener(
'submit',
handleSubmit
);
restoreDraft();- Creating custom error classes.
- Extending the Error class.
- Creating ValidationError.
- Creating SubmissionError.
- Adding custom error properties.
- Throwing validation errors.
- Using try...catch...finally.
- Checking errors with instanceof.
- Handling different errors differently.
- Validating form input.
- Displaying field-specific errors.
- Creating user-friendly messages.
- Logging technical error details.
- Using async and await.
- Handling Promise rejection.
- Simulating asynchronous failures.
- Using finally for interface cleanup.
- Disabling buttons during operations.
- Restoring buttons after success or failure.
- Saving temporary form drafts.
- Handling Session Storage errors.
- Handling JSON parsing errors.
- Using fallback values.
- Preserving user input.
- Removing temporary data after success.
- Separating developer information from user messages.
- Recovering from expected failures.
- Handling unexpected failures safely.
Application Flow
USER ACTION
│
▼
Collect Input
│
▼
try
│
▼
Validate Input
│
├── Invalid
│ │
│ ▼
│ throw
│ │
│ ▼
│ ValidationError
│
▼
Start Operation
│
▼
Async Request
│
├── Success
│ │
│ ▼
│ Update UI
│
└── Failure
│
▼
throw Error
│
▼
catch
│
├── ValidationError
│
├── Expected Error
│
└── Unexpected Error
│
▼
Show Appropriate Message
│
▼
finally
│
▼
Restore Interface
│
▼
Continue ApplicationReal-World Applications
Forms
Validate input and display useful field-specific messages.
API Requests
Handle network failures, HTTP errors, invalid responses, and retries.
Browser Storage
Handle unavailable storage, invalid JSON, and failed writes.
Authentication
Handle invalid credentials, expired sessions, and unavailable services.
E-Commerce
Handle unavailable products, payment failures, and checkout validation.
File Processing
Handle unsupported files, invalid content, and processing failures.
Dashboards
Use fallback states when one data source fails.
Games
Handle missing assets, invalid state, and save-data problems.
Web Applications
Prevent one failed operation from breaking the entire interface.
Common Beginner Mistakes
- Ignoring errors completely.
- Using try...catch around every line of code.
- Catching errors without doing anything.
- Using empty catch blocks.
- Hiding unexpected programming errors.
- Throwing plain strings.
- Throwing numbers instead of Error objects.
- Showing technical stack traces directly to users.
- Using the same message for every error.
- Not checking error types.
- Not using instanceof when custom error types matter.
- Catching an error too far away from the operation.
- Handling an error where recovery is impossible.
- Failing to re-throw unknown errors.
- Using finally for normal error-specific logic.
- Forgetting that finally runs after success and failure.
- Returning from finally unnecessarily.
- Expecting try...catch to catch future asynchronous errors automatically.
- Forgetting to await a Promise inside try.
- Forgetting Promise catch().
- Ignoring rejected Promises.
- Assuming fetch rejects for every HTTP error status.
- Not checking response.ok.
- Assuming every API response contains valid JSON.
- Parsing JSON without handling invalid syntax.
- Using JSON.parse() on missing values carelessly.
- Ignoring browser storage failures.
- Assuming localStorage is always available.
- Assuming sessionStorage is always available.
- Not validating stored data.
- Trusting user input automatically.
- Using errors instead of ordinary conditional logic for every situation.
- Using vague error messages.
- Creating custom errors without useful properties.
- Forgetting to call super(message) in a custom Error class.
- Not setting useful custom error names.
- Losing the original error when creating a higher-level error.
- Logging sensitive information.
- Logging passwords.
- Logging authentication secrets.
- Logging payment details.
- Exposing internal implementation details.
- Retrying operations forever.
- Retrying permanent failures.
- Retrying without limits.
- Retrying non-idempotent operations carelessly.
- Not providing fallback values.
- Using fallback values that hide critical failures.
- Failing to restore loading indicators.
- Leaving buttons disabled after errors.
- Losing user input after failed submissions.
- Not testing failure scenarios.
- Testing only successful application flows.
- Ignoring global unexpected errors.
- Using global error handlers as a replacement for local handling.
- Not distinguishing developer logs from user messages.
- Using alert() for every error.
- Failing to clean up temporary state.
- Failing to preserve useful context when logging errors.
- Not including timestamps in important logs.
- Swallowing errors that should stop the operation.
- Continuing with invalid state after a critical failure.
Best Practices
- Handle errors close to the operation when recovery is possible.
- Use try...catch for operations that can meaningfully fail.
- Keep try blocks focused.
- Avoid wrapping unrelated code in one large try block.
- Throw Error objects instead of plain strings.
- Use clear and specific error messages.
- Use built-in error types when appropriate.
- Create custom error classes for important application-specific failures.
- Call super(message) in custom Error classes.
- Set meaningful custom error names.
- Add useful custom properties such as codes and fields.
- Use instanceof to distinguish error types.
- Handle known errors appropriately.
- Re-throw errors that cannot be handled correctly.
- Preserve original errors with cause when useful.
- Use finally for cleanup.
- Restore loading states in finally.
- Restore disabled controls after operations.
- Validate user input before processing.
- Validate data from browser storage.
- Validate API responses.
- Handle JSON parsing failures.
- Check for missing data.
- Provide fallback values where appropriate.
- Do not use fallbacks to hide critical failures.
- Check response.ok when using fetch.
- Distinguish network failures from HTTP failures.
- Use catch() for Promise rejection.
- Use try...catch with awaited operations.
- Avoid unhandled Promise rejections.
- Use global handlers only as a final safety net.
- Log unexpected failures.
- Include useful context in developer logs.
- Include timestamps when useful.
- Avoid logging sensitive information.
- Do not expose stack traces to users.
- Show clear user-friendly messages.
- Tell users what they can do next.
- Preserve user input after recoverable failures.
- Use limited retries for temporary failures.
- Do not retry forever.
- Use appropriate delays for repeated retries.
- Avoid careless retries for operations with side effects.
- Use safe storage functions.
- Use safe JSON parsing functions.
- Test success scenarios.
- Test validation failures.
- Test network failures.
- Test HTTP failures.
- Test invalid JSON.
- Test missing DOM elements.
- Test storage failures.
- Test rejected Promises.
- Test unexpected errors.
- Test cleanup behavior.
- Test retry limits.
- Keep developer errors and user messages separate.
- Use error handling to improve reliability, not to hide programming mistakes.
Frequently Asked Questions
What is an error?
An error is a problem that interrupts normal program execution or prevents the expected result.
What are the main categories of programming errors?
Syntax errors, runtime errors, and logical errors.
What is a syntax error?
An error caused by code that does not follow the grammar rules of the language.
What is a runtime error?
An error that occurs while the program is executing.
What is a logical error?
A mistake where the program runs but produces the wrong result.
What does try do?
It contains code that may throw an error.
What does catch do?
It handles errors thrown from the associated try block.
What happens to the remaining try code after an error?
It is skipped.
What is the catch parameter?
It normally contains the thrown error value.
What does error.name contain?
The error type name.
What does error.message contain?
A description of the problem.
What does error.stack contain?
Debugging information about where the error occurred.
Can catch be used without an error parameter?
Yes, when the error value is not needed.
What does finally do?
It runs after try and catch processing whether the operation succeeds or fails.
When should finally be used?
For cleanup and interface restoration that must happen after success or failure.
What does throw do?
It intentionally throws a value as an error.
Should I throw strings?
Usually no. Throw Error objects for better debugging and consistency.
How do I create an Error object?
Use new Error(message).
What is ReferenceError?
An error commonly caused by accessing an identifier that does not exist.
What is TypeError?
An error caused by using a value in an inappropriate way for its type.
What is SyntaxError?
An error representing invalid syntax.
What is RangeError?
An error caused by a value outside an allowed range.
What is URIError?
An error related to invalid URI processing.
What is AggregateError?
An error object that groups multiple errors.
How do I check an error type?
Use instanceof when appropriate.
Can I create custom errors?
Yes.
How do I create a custom error class?
Create a class that extends Error and call super(message).
What is error cause?
It allows a higher-level error to preserve the original error that caused it.
What does re-throwing mean?
Catching an error and throwing it again so another part of the application can handle it.
Can try...catch be nested?
Yes.
Can functions throw errors?
Yes.
Can JSON.parse() throw an error?
Yes. Invalid JSON can throw a SyntaxError.
Can browser storage operations fail?
Yes.
Does fetch reject for every 404 or 500 response?
No. HTTP status should be checked using response.ok or response.status.
How do I handle Promise errors?
Use catch() or use try...catch with await.
Can try...catch handle await errors?
Yes, when the Promise is awaited inside the try block.
What is an unhandled Promise rejection?
A rejected Promise without appropriate rejection handling.
Should users see stack traces?
Normally no.
Should developers log technical details?
Yes, when useful, while avoiding sensitive information.
What is error recovery?
Continuing safely through retries, fallbacks, cached data, alternative flows, or useful messages.
Should every error be retried?
No.
Should retries be unlimited?
No.
Should I catch every error?
No. Catch errors where you can handle, recover, add context, or log them appropriately.
Should catch blocks be empty?
Usually no, because empty catch blocks hide failures.
Can error handling fix logical errors automatically?
No. Logical errors require testing, debugging, and correcting the program logic.
Key Takeaways
- Errors interrupt normal program execution.
- Syntax errors involve invalid language syntax.
- Runtime errors occur during execution.
- Logical errors produce incorrect results without necessarily throwing.
- Unhandled errors can stop the current execution flow.
- try contains code that may fail.
- catch handles thrown errors.
- Remaining try statements are skipped after an error.
- Error objects contain useful failure information.
- error.name identifies the error type.
- error.message describes the problem.
- error.stack helps with debugging.
- Catch parameters can be omitted when unnecessary.
- finally runs after success or failure.
- finally is useful for cleanup.
- throw creates an intentional failure.
- Error objects should normally be thrown instead of strings.
- The Error constructor creates generic errors.
- JavaScript provides multiple built-in error types.
- ReferenceError relates to invalid references.
- TypeError relates to inappropriate value operations.
- SyntaxError represents invalid syntax.
- RangeError represents invalid ranges.
- URIError relates to URI processing.
- AggregateError groups multiple failures.
- instanceof can identify error types.
- Applications can create custom error classes.
- Custom errors can contain application-specific properties.
- Error causes can preserve original failures.
- Unknown errors can be re-thrown.
- try...catch blocks can be nested.
- Functions can throw errors.
- Input should be validated.
- Forms can use field-specific validation errors.
- JSON parsing can fail.
- Safe JSON parsing can provide fallback values.
- DOM operations should validate required elements.
- Browser storage operations can fail.
- Network requests can fail.
- HTTP failures should be checked explicitly.
- Promises can reject.
- Promise catch() handles rejections.
- await errors can be handled with try...catch.
- Multiple asynchronous operations require careful error handling.
- Global error handlers are a final safety net.
- Unhandled Promise rejections should be monitored.
- Technical errors should be logged appropriately.
- Users should receive clear helpful messages.
- Developer information and user messages should be separated.
- Applications can recover using retries and fallbacks.
- Retries should be limited.
- User input should be preserved when possible.
- Cleanup should happen after both success and failure.
- Error handling improves application reliability.
Summary
JavaScript errors represent problems that interrupt normal execution or prevent a program from producing the expected result. Errors are commonly categorized as syntax errors, runtime errors, and logical errors.
The try statement contains code that may fail, while catch handles errors thrown during that execution. When an error occurs inside try, the remaining statements in that try block are skipped and control moves to catch.
Error objects provide useful information through properties such as name, message, and stack. These properties help applications identify failures and help developers understand where problems occurred.
The finally block runs after try and catch processing whether the operation succeeds or fails. It is especially useful for cleanup, restoring loading states, enabling buttons, and other operations that must always happen.
The throw statement allows applications to create intentional failures when rules are violated. Error objects should normally be thrown instead of plain strings or numbers because they provide consistent error names, messages, and stack traces.
JavaScript includes built-in error types such as Error, ReferenceError, TypeError, SyntaxError, RangeError, URIError, and AggregateError. Applications can also create custom error classes for domain-specific failures such as validation errors and submission errors.
Error handling is important for form validation, JSON parsing, DOM operations, browser storage, network requests, HTTP responses, Promises, and async functions. Each operation should handle failures at the level where meaningful recovery is possible.
Reliable applications separate technical developer information from user-facing messages. Developers may need error names, stack traces, codes, timestamps, and context, while users need clear explanations and useful next steps.
Error recovery can include fallback values, retries, cached data, alternative flows, preserved user input, and safe interface states. Recovery should be intentional and should never silently hide critical failures.
Mastering JavaScript error handling allows you to build applications that remain stable, understandable, and useful even when unexpected conditions occur.
- You understand what JavaScript errors are.
- You understand why error handling matters.
- You understand how JavaScript handles errors.
- You understand syntax errors.
- You understand runtime errors.
- You understand logical errors.
- You understand normal error flow.
- You can use try.
- You can use catch.
- You can use try...catch.
- You understand try...catch execution flow.
- You understand what happens to code after an error.
- You can inspect Error objects.
- You can use error.name.
- You can use error.message.
- You can use error.stack.
- You understand optional catch binding.
- You can use finally.
- You can use try...catch...finally.
- You understand finally execution flow.
- You know when to use finally.
- You can use throw.
- You understand why plain strings should not normally be thrown.
- You understand why numbers should not normally be thrown.
- You can throw Error objects.
- You understand why Error objects are useful.
- You can use the Error constructor.
- You understand built-in error types.
- You understand Error.
- You understand ReferenceError.
- You understand TypeError.
- You understand SyntaxError.
- You understand RangeError.
- You understand URIError.
- You understand AggregateError.
- You can compare error types.
- You can check errors with instanceof.
- You can create custom errors.
- You can create custom error classes.
- You can create ValidationError.
- You can create NotFoundError.
- You understand error causes.
- You can re-throw errors.
- You can use nested try...catch.
- You can handle function errors.
- You can validate input.
- You can handle form validation errors.
- You can handle JSON parsing errors.
- You can safely parse JSON.
- You can handle DOM errors.
- You can handle browser storage errors.
- You can handle network errors.
- You can handle HTTP errors.
- You understand Promise errors.
- You can use Promise catch().
- You can handle async and await errors.
- You can handle multiple asynchronous operations.
- You understand global error handling.
- You understand unhandled Promise rejections.
- You can log errors.
- You understand console error methods.
- You can create user-friendly messages.
- You understand developer and user error separation.
- You understand error recovery.
- You can use fallback values.
- You can implement retry logic.
- You understand common error handling patterns.
- You can build a complete error-handling application.
- You have completed the Core JavaScript course.