javascript
exercises
exercises.js⚡javascript
/**
* =====================================================
* 4.6 ERROR HANDLING WITH TRY-CATCH - EXERCISES
* =====================================================
* Practice error handling
*/
/**
* Exercise 1: Safe JSON Parse
*
* Create a function that safely parses JSON.
* Return the parsed object or a default value if parsing fails.
*/
function safeJSONParse(jsonString, defaultValue = null) {
// TODO: Use try-catch to safely parse JSON
}
// Test cases:
console.log('Exercise 1:');
console.log(safeJSONParse('{"name": "John"}')); // { name: "John" }
console.log(safeJSONParse('invalid')); // null
console.log(safeJSONParse('bad', [])); // []
/**
* Exercise 2: Division with Validation
*
* Create a divide function that:
* - Throws TypeError if arguments aren't numbers
* - Throws RangeError if dividing by zero
* - Returns the result otherwise
*/
function safeDivide(a, b) {
// TODO: Implement with proper error throwing
}
// Test cases:
console.log('\nExercise 2:');
try {
console.log(safeDivide(10, 2)); // 5
console.log(safeDivide(10, 0)); // Should throw
} catch (e) {
console.log(`${e.name}: ${e.message}`);
}
/**
* Exercise 3: Validate Email
*
* Create a function that validates an email:
* - Throws if email is not a string
* - Throws if email doesn't contain @
* - Throws if email is empty
* - Returns true if valid
*/
function validateEmail(email) {
// TODO: Implement email validation with throws
}
// Test cases:
console.log('\nExercise 3:');
try {
console.log(validateEmail('user@example.com')); // true
console.log(validateEmail('invalid')); // throws
} catch (e) {
console.log(`Error: ${e.message}`);
}
/**
* Exercise 4: Array Access with Error Handling
*
* Create a function that safely gets an array element.
* Returns the element or undefined if index is out of bounds.
* Throws if arr is not an array.
*/
function safeArrayAccess(arr, index) {
// TODO: Implement safe array access
}
// Test cases:
console.log('\nExercise 4:');
console.log(safeArrayAccess([1, 2, 3], 1)); // 2
console.log(safeArrayAccess([1, 2, 3], 10)); // undefined
try {
console.log(safeArrayAccess('not array', 0)); // throws
} catch (e) {
console.log(`Error: ${e.message}`);
}
/**
* Exercise 5: Custom Validation Error
*
* Create a ValidationError class that includes:
* - message: error message
* - field: the field that failed validation
*
* Then create a validateUser function that uses it.
*/
class ValidationError extends Error {
// TODO: Implement custom error class
}
function validateUser(user) {
// TODO: Validate user object and throw ValidationError
// Required: name (string, min 2 chars), age (number, min 0)
}
// Test cases:
console.log('\nExercise 5:');
try {
validateUser({ name: 'A', age: 25 });
} catch (e) {
if (e instanceof ValidationError) {
console.log(`Validation error on '${e.field}': ${e.message}`);
}
}
/**
* Exercise 6: Try-Finally Cleanup
*
* Create a function that simulates opening, using, and closing a resource.
* The resource should always be closed even if an error occurs.
*/
function useResource(shouldFail) {
let resource = null;
// TODO: Implement with try-finally
// - Set resource to { open: true }
// - If shouldFail, throw an error
// - In finally, log "Resource closed" and set resource.open to false
// - Return "Success" if no error
}
// Test cases:
console.log('\nExercise 6:');
try {
console.log(useResource(false)); // Should log "Resource closed", return "Success"
console.log(useResource(true)); // Should log "Resource closed", throw error
} catch (e) {
console.log(`Error: ${e.message}`);
}
/**
* Exercise 7: Error Type Handler
*
* Create a function that takes an error and returns a user-friendly message
* based on the error type.
*/
function getErrorMessage(error) {
// TODO: Return different messages based on error type
// TypeError: "There was a type mismatch"
// RangeError: "Value was out of acceptable range"
// SyntaxError: "Invalid syntax detected"
// Other: "An unexpected error occurred"
}
// Test cases:
console.log('\nExercise 7:');
console.log(getErrorMessage(new TypeError('test')));
console.log(getErrorMessage(new RangeError('test')));
console.log(getErrorMessage(new SyntaxError('test')));
console.log(getErrorMessage(new Error('test')));
/**
* Exercise 8: Safe Function Wrapper
*
* Create a higher-order function that wraps any function
* and catches any errors, returning undefined instead.
*/
function makeSafe(fn) {
// TODO: Return a new function that catches errors
}
// Test cases:
console.log('\nExercise 8:');
const riskyFn = (x) => {
if (x < 0) throw new Error('Negative!');
return x * 2;
};
const safeFn = makeSafe(riskyFn);
console.log(safeFn(5)); // 10
console.log(safeFn(-5)); // undefined
/**
* Exercise 9: Multiple Validation
*
* Create a function that validates a form object and collects
* ALL errors instead of stopping at the first one.
* Return { valid: true } or { valid: false, errors: [...] }
*/
function validateForm(form) {
// TODO: Validate form with all errors collected
// Required fields: name (string), email (contains @), age (number >= 0)
}
// Test cases:
console.log('\nExercise 9:');
console.log(validateForm({ name: 'John', email: 'john@example.com', age: 25 }));
// { valid: true }
console.log(validateForm({ name: '', email: 'invalid', age: -5 }));
// { valid: false, errors: [...3 errors...] }
/**
* Exercise 10: Retry with Error Handling
*
* Create a function that attempts an operation up to maxRetries times.
* Returns the result if successful, throws if all retries fail.
*/
function retry(fn, maxRetries = 3) {
// TODO: Implement retry logic with try-catch
}
// Test cases:
console.log('\nExercise 10:');
let attempts = 0;
const unreliableFn = () => {
attempts++;
if (attempts < 3) throw new Error('Failed');
return 'Success!';
};
try {
attempts = 0;
console.log(retry(unreliableFn, 5)); // "Success!" (on 3rd attempt)
} catch (e) {
console.log(`Error: ${e.message}`);
}
// =====================================================
// BONUS CHALLENGES
// =====================================================
/**
* Bonus 1: Error Chain
*
* Create a function that preserves the original error
* while adding context.
*/
class ChainedError extends Error {
// TODO: Implement error that includes cause
}
function processData(data) {
// TODO: Parse data and add context to any errors
}
/**
* Bonus 2: Async-Like Error Handling
*
* Create a Result type that wraps success/failure
* without using try-catch at the call site.
*/
class Result {
// TODO: Implement Ok and Err result types
}
/**
* Bonus 3: Error Boundary
*
* Create a function that runs multiple operations
* and returns partial results for successful ones
* along with errors for failed ones.
*/
function runOperations(operations) {
// TODO: Run all operations, collect results and errors
}
console.log('\nBonus 3:');
const ops = [
() => 1 + 1,
() => {
throw new Error('Failed');
},
() => 2 + 2,
];
// console.log(runOperations(ops));
// { results: [2, 4], errors: [Error: "Failed"], indices: { success: [0, 2], failed: [1] } }
// =====================================================
// SOLUTIONS (Uncomment to check your answers)
// =====================================================
/*
// Exercise 1 Solution:
function safeJSONParse(jsonString, defaultValue = null) {
try {
return JSON.parse(jsonString);
} catch {
return defaultValue;
}
}
// Exercise 2 Solution:
function safeDivide(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Arguments must be numbers");
}
if (b === 0) {
throw new RangeError("Cannot divide by zero");
}
return a / b;
}
// Exercise 3 Solution:
function validateEmail(email) {
if (typeof email !== "string") {
throw new TypeError("Email must be a string");
}
if (email.length === 0) {
throw new Error("Email cannot be empty");
}
if (!email.includes("@")) {
throw new Error("Email must contain @");
}
return true;
}
// Exercise 4 Solution:
function safeArrayAccess(arr, index) {
if (!Array.isArray(arr)) {
throw new TypeError("First argument must be an array");
}
if (index < 0 || index >= arr.length) {
return undefined;
}
return arr[index];
}
// Exercise 5 Solution:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateUser(user) {
if (typeof user.name !== "string" || user.name.length < 2) {
throw new ValidationError("Name must be at least 2 characters", "name");
}
if (typeof user.age !== "number" || user.age < 0) {
throw new ValidationError("Age must be a non-negative number", "age");
}
return true;
}
// Exercise 6 Solution:
function useResource(shouldFail) {
let resource = null;
try {
resource = { open: true };
if (shouldFail) {
throw new Error("Operation failed");
}
return "Success";
} finally {
if (resource) {
console.log("Resource closed");
resource.open = false;
}
}
}
// Exercise 7 Solution:
function getErrorMessage(error) {
if (error instanceof TypeError) {
return "There was a type mismatch";
} else if (error instanceof RangeError) {
return "Value was out of acceptable range";
} else if (error instanceof SyntaxError) {
return "Invalid syntax detected";
} else {
return "An unexpected error occurred";
}
}
// Exercise 8 Solution:
function makeSafe(fn) {
return function(...args) {
try {
return fn(...args);
} catch {
return undefined;
}
};
}
// Exercise 9 Solution:
function validateForm(form) {
const errors = [];
if (typeof form.name !== "string" || form.name.length === 0) {
errors.push("Name is required");
}
if (typeof form.email !== "string" || !form.email.includes("@")) {
errors.push("Valid email is required");
}
if (typeof form.age !== "number" || form.age < 0) {
errors.push("Age must be a non-negative number");
}
return errors.length === 0
? { valid: true }
: { valid: false, errors };
}
// Exercise 10 Solution:
function retry(fn, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return fn();
} catch (error) {
lastError = error;
}
}
throw lastError;
}
// Bonus 1 Solution:
class ChainedError extends Error {
constructor(message, cause) {
super(message);
this.name = "ChainedError";
this.cause = cause;
}
}
function processData(data) {
try {
return JSON.parse(data);
} catch (error) {
throw new ChainedError("Failed to process data", error);
}
}
// Bonus 3 Solution:
function runOperations(operations) {
const results = [];
const errors = [];
const indices = { success: [], failed: [] };
for (let i = 0; i < operations.length; i++) {
try {
results.push(operations[i]());
indices.success.push(i);
} catch (error) {
errors.push(error);
indices.failed.push(i);
}
}
return { results, errors, indices };
}
*/