Error Handling in JavaScript
Error Handling in JavaScript
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
To wrap up phase 4, let's build robust error handling for our entire file system – including our own, meaningful error classes for the budget app.
try, catch, and finally
try {
const data = JSON.parse('{ invalid json');
console.log(data);
} catch (error) {
console.error('Error parsing:', error.message);
} finally {
console.log('Always runs - error or not'); // e.g. for cleanup work
}try/catch with async/await
The big practical advantage of async/await over .then()/.catch() chains: error handling uses the SAME try/catch as synchronous code, instead of needing a separate .catch() syntax:
async function loadTransactionsSafely(path) {
try {
const content = await readFile(path, 'utf-8');
return JSON.parse(content);
} catch (error) {
console.error(`Could not load ${path}:`, error.message);
return []; // a sensible fallback instead of crashing
}
}Custom error classes
The built-in Error class can be extended to model MORE MEANINGFUL, specific error types for our budget app:
export class BudgetAppError extends Error {
constructor(message) {
super(message);
this.name = 'BudgetAppError'; // appears in stack traces instead of 'Error'
}
}
export class InvalidTransactionError extends BudgetAppError {
constructor(reason) {
super(`Invalid transaction: ${reason}`);
this.name = 'InvalidTransactionError';
}
}
export class FileNotFoundError extends BudgetAppError {
constructor(path) {
super(`File not found: ${path}`);
this.name = 'FileNotFoundError';
this.path = path; // additional, specific information on the error
}
}Distinguishing error types in a catch block
import { InvalidTransactionError, FileNotFoundError } from './errors.js';
function validateTransaction(transaction) {
if (typeof transaction.amount !== 'number' || Number.isNaN(transaction.amount)) {
throw new InvalidTransactionError('Amount must be a valid number');
}
if (!transaction.description) {
throw new InvalidTransactionError('Description is missing');
}
}
try {
validateTransaction({ description: 'Rent', amount: 'not-a-number' });
} catch (error) {
if (error instanceof InvalidTransactionError) {
console.error('Validation error:', error.message);
} else if (error instanceof FileNotFoundError) {
console.error('File problem:', error.path);
} else {
throw error; // unknown error type - rethrow instead of swallowing it!
}
}Achtung: instanceof uses the prototype chain from chapter 20 – WHICH is why this distinction works reliably even across several inheritance levels (InvalidTransactionError → BudgetAppError → Error). SILENTLY ignoring an unknown error type in a catch block, instead of rethrowing it, hides real bugs.
The complete file storage system
Let's now combine everything from phase 4 into a robust storage system for our budget app:
import { readFile, writeFile } from 'node:fs/promises';
import { FileNotFoundError, BudgetAppError } from './errors.js';
export async function loadTransactions(path) {
try {
const content = await readFile(path, 'utf-8');
return JSON.parse(content);
} catch (error) {
if (error.code === 'ENOENT') {
throw new FileNotFoundError(path);
}
throw new BudgetAppError(`Could not read ${path}: ${error.message}`);
}
}
export async function saveTransactions(path, transactions) {
try {
await writeFile(path, JSON.stringify(transactions, null, 2), 'utf-8');
} catch (error) {
throw new BudgetAppError(`Could not write ${path}: ${error.message}`);
}
}import { loadTransactions, saveTransactions } from './fileStorage.js';
import { FileNotFoundError } from './errors.js';
try {
const transactions = await loadTransactions('data/transactions.json');
console.log(`${transactions.length} transactions loaded`);
transactions.push({ description: 'Movies', amount: -18, category: 'Leisure' });
await saveTransactions('data/transactions.json', transactions);
console.log('Saved!');
} catch (error) {
if (error instanceof FileNotFoundError) {
console.log('No file yet, starting with an empty budget.');
} else {
console.error('Unexpected error:', error.message);
}
}With that, phase 4 (asynchrony) is complete! In phase 5, we round off the project with a deeper look at JSON persistence, automated testing, and a final best-practices wrap-up.