Error Handling
Good error handling ensures the application keeps running even when mistakes happen. Without error handling, a single error can stop the entire application. JavaScript provides try...catch...finally, throw, and the Error class to handle this.
Code Example
Basic try...catch...finally
try {
// Code that might throw an error
const data = JSON.parse('{"name": "Mike"}');
console.log(data.name);
const result = data.studentId.missing; // TypeError!
} catch (error) {
console.error('Terjadi error:', error.message);
} finally {
console.log('This always runs');
}
// Output:
// "Mike"
// "Terjadi error: Cannot read properties of undefined"
// "This always runs"
Throwing Errors
function bagi(a, b) {
if (b === 0) {
throw new Error('Division by zero is not allowed');
}
return a / b;
}
try {
const result = bagi(10, 0);
console.log(result);
} catch (error) {
console.error(error.message);
}
// Output: "Division by zero is not allowed"
Code Explanation
Types of Errors
// TypeError — data type mismatch
try {
null.name;
} catch (e) {
console.log(e instanceof TypeError); // true
}
// ReferenceError — variable not defined
try {
console.log(nonExistentVariable);
} catch (e) {
console.log(e instanceof ReferenceError); // true
}
// SyntaxError — invalid code
try {
eval('function { invalid }');
} catch (e) {
console.log(e instanceof SyntaxError); // true
}
// RangeError — value out of range
try {
const arr = new Array(-1);
} catch (e) {
console.log(e instanceof RangeError); // true
}
// URIError — URI function used incorrectly
try {
decodeURIComponent('%');
} catch (e) {
console.log(e instanceof URIError); // true
}
Custom Error Class
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class ApiError extends Error {
constructor(statusCode, message) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
}
}
// Using a custom error
function validationForm(data) {
const errors = [];
if (!data.studentId || data.studentId.length !== 8) {
errors.push(new ValidationError('studentId', 'StudentID must be 8 digits'));
}
if (!data.name || data.name.trim().length < 3) {
errors.push(new ValidationError('name', 'Name at least 3 characters'));
}
if (!data.email.includes('@')) {
errors.push(new ValidationError('email', 'Invalid email'));
}
return errors;
}
// Handling it
try {
const errors = validationForm({ studentId: '123', name: 'A', email: 'invalid' });
if (errors.length > 0) {
throw errors; // throw the array of errors
}
// process the save...
} catch (error) {
if (Array.isArray(error)) {
error.forEach(err => {
console.error(`${err.field}: ${err.message}`);
});
}
}
Real-World Examples
Error Handling for Form Validation
class FormValidator {
constructor(formElement) {
this.form = formElement;
this.errors = [];
}
required(fieldName, label) {
const value = this.form.querySelector(`[name="${fieldName}"]`).value;
if (!value || value.trim() === '') {
this.errors.push({ field: fieldName, message: `${label} is required` });
}
return this;
}
minLength(fieldName, label, min) {
const value = this.form.querySelector(`[name="${fieldName}"]`).value;
if (value && value.length < min) {
this.errors.push({
field: fieldName,
message: `${label} at least ${min} characters`
});
}
return this;
}
pattern(fieldName, label, regex, message) {
const value = this.form.querySelector(`[name="${fieldName}"]`).value;
if (value && !regex.test(value)) {
this.errors.push({ field: fieldName, message });
}
return this;
}
validate() {
this.errors = [];
return this;
}
showErrors() {
// Remove previous errors
this.form.querySelectorAll('.error-message').forEach(el => el.remove());
this.form.querySelectorAll('.input-error').forEach(el => el.classList.remove('input-error'));
this.errors.forEach(err => {
const input = this.form.querySelector(`[name="${err.field}"]`);
input.classList.add('input-error');
const errorEl = document.createElement('span');
errorEl.className = 'error-message';
errorEl.textContent = err.message;
input.parentNode.appendChild(errorEl);
});
return this.errors.length === 0;
}
}
// Usage
const form = document.querySelector('#form-student');
form.addEventListener('submit', function (e) {
e.preventDefault();
const validator = new FormValidator(this)
.validate()
.required('studentId', 'StudentID')
.required('name', 'Name')
.minLength('studentId', 'StudentID', 8)
.pattern('email', 'Email', /^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Invalid email');
if (!validator.showErrors()) return; // there are errors
// No errors — process the submit
submitForm(this);
});
Error Handling for API Calls
async function apiCall(url, options = {}) {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`,
...options.headers
}
});
// Handle HTTP errors
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
switch (response.status) {
case 401:
throw new ApiError(401, 'Session ended, please log in again');
case 403:
throw new ApiError(403, 'You do not have access');
case 404:
throw new ApiError(404, 'Data not found');
case 422:
throw new ApiError(422, errorData.message || 'Invalid data');
case 500:
throw new ApiError(500, 'Server error, please try again later');
default:
throw new ApiError(response.status, `Error ${response.status}`);
}
}
return await response.json();
} catch (error) {
if (error instanceof ApiError) {
// Show the specific error
showNotification(error.message, 'error');
if (error.statusCode === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
} else {
// Network error or some other error
showNotification('Failed to connect to server', 'error');
}
throw error;
}
}
// Usage
async function loadStudent() {
try {
const data = await apiCall('/api/student');
renderTable(data);
} catch (error) {
// The error was already handled in apiCall
console.error('Load failed:', error);
}
}
Error Boundary Pattern
// Wrap every operation with consistent error handling
function withErrorHandling(fn, fallbackValue = null) {
return async function (...args) {
try {
return await fn(...args);
} catch (error) {
console.error(`Error in ${fn.name}:`, error);
if (error instanceof ApiError) {
showNotification(error.message, 'error');
} else {
showNotification('An unexpected error occurred', 'error');
}
return fallbackValue;
}
};
}
// Function decorator
const loadProfile = withErrorHandling(async function () {
const res = await fetch('/api/profile');
if (!res.ok) throw new ApiError(res.status, 'Failed to load profile');
return res.json();
}, null);
const loadCourses = withErrorHandling(async function () {
const res = await fetch('/api/courses');
if (!res.ok) throw new ApiError(res.status, 'Failed to load courses');
return res.json();
}, []);
// All errors are handled consistently
const profile = await loadProfile();
const courses = await loadCourses();
Best Practices
// 1. Don't catch without handling
try {
processData();
} catch (error) {
// DON'T do this — the error gets "swallowed" without any action
}
// 2. Always log errors for debugging
catch (error) {
console.error('Details error:', error);
showNotification('An error occurred');
}
// 3. Use specific errors
throw new ValidationError('studentId', 'StudentID must be 8 digits'); // good
throw new Error('Something went wrong'); // bad — too generic
// 4. Clean up resources in finally
try {
const conn = await openConnection();
await conn.query(sql);
} catch (error) {
console.error('Query failed:', error);
} finally {
await closeConnection(); // always closed
}
// 5. Don't use errors for flow control
// Bad:
try {
const result = parseJSON(input);
} catch {
// relying on an error to detect "parse failed"
}
// Good:
const result = parseJSON(input);
if (!result) {
// handle invalid JSON in a clear, explicit way
}
Conclusion
try...catch...finally is the foundation of error handling in JavaScript. Use custom error classes for more specific errors (ValidationError, ApiError). Always log errors for debugging, clean up resources in finally, and never swallow errors without taking action. For a university application, handle API errors with clear messages and never let a single error stop the entire application.