Exercises
This chapter introduces no new concepts. It’s a chance to practice everything from Module 8: catching errors with try/catch/finally, raising your own errors with throw, and writing and using custom error classes.
Work through these in your own console (browser devtools or node), in order.
Part I: try, catch, and finally
1. Fix the crash.
function loadSettings(json) {
const settings = JSON.parse(json);
console.log("Loaded settings:", settings);
}
loadSettings('{"volume": 80}');
loadSettings("not valid json");
console.log("Program finished");
Run this as-is first: the second call crashes with a SyntaxError, and "Program finished" never prints. Rewrite loadSettings so a bad json argument logs the error with console.error() instead of crashing. Check: both calls should complete, and "Program finished" should print at the end.
2. Write safeDivide.
Write safeDivide(a, b) that throws new Error("Cannot divide by zero") when b is 0, and otherwise returns a / b. Then call it inside a try/catch, once with safeDivide(10, 2) and once with safeDivide(10, 0). Check: the first call prints Result: 5; the second is caught and prints Error: Cannot divide by zero.
3. Prove finally runs both times.
Write attemptConnection(shouldFail): it throws new Error("Connection failed") when shouldFail is true, and otherwise returns "connected". Wrap the risky line in try/finally (no catch needed inside the function) where finally logs "Connection attempt finished". Call attemptConnection(false) and attemptConnection(true) (the second call wrapped in its own outer try/catch so your script doesn’t crash). Check: "Connection attempt finished" should print after both calls, once before the successful return and once before the error propagates out.
4. Catch and recover.
function formatPrice(price) {
return price.toFixed(2);
}
console.log(formatPrice(19.99));
console.log(formatPrice("19.99"));
The second call throws a TypeError, because strings don’t have a .toFixed() method. Fix formatPrice with try/catch: on success, return price.toFixed(2) as before; in the catch block, convert with Number(price).toFixed(2) instead. Check: both formatPrice(19.99) and formatPrice("19.99") should return the string "19.99".
5. Rethrow and still clean up.
Write riskyOperation(shouldFail). Inside a try, throw new Error("Something broke") if shouldFail is true, otherwise return "ok". In the catch block, log the error’s message, then rethrow it with throw error. Add a finally block that logs "finally: riskyOperation cleanup". Call riskyOperation(false) and riskyOperation(true), each inside its own outer try/catch. Check: the cleanup line should print after both calls, even though the second call’s error is rethrown and only caught by the outer catch.
Part II: Throwing Your Own Errors
6. Validate a password.
Write validatePassword(password): throw new Error("Password must be at least 8 characters") if password.length < 8, otherwise return true. Check: validatePassword("abc") throws with that exact message; validatePassword("supersecure123") returns true.
7. Throw instead of returning nothing.
function findUser(users, id) {
return users.find((u) => u.id === id);
}
As written, calling this with an id that doesn’t exist returns undefined, which is easy to accidentally use as if it were a real user. Rewrite it to throw new Error("No user found with id " + id) when no match is found. Check with users = [{ id: 1, name: "Erin" }, { id: 2, name: "Jordan" }]: findUser(users, 1) returns Erin’s object; findUser(users, 99) throws with the message No user found with id 99.
8. Two different failure conditions.
Write checkAge(age) that throws new Error("Age cannot be negative") when age < 0, throws new Error("Age is not realistic") when age > 150, and otherwise returns age. Check: checkAge(30) returns 30, checkAge(-1) and checkAge(200) each throw with their respective messages.
9. Fix the string throw.
function riskyThing() {
throw "Something went wrong";
}
try {
riskyThing();
} catch (error) {
console.log("error.message:", error.message);
}
Run this first: error.message logs undefined, because a plain string has no .message property. Fix riskyThing to throw new Error("Something went wrong") instead. Check: after the fix, error.message should log the actual text Something went wrong.
Part III: Custom Error Classes
10. Your first custom error.
Write a class NotFoundError that extends Error and sets this.name = "NotFoundError" in its constructor. Rewrite findUser from exercise 7 to throw a NotFoundError instead of a plain Error. Check: catching findUser(users, 99) should show error.name === "NotFoundError" and error instanceof NotFoundError should be true.
11. A second custom error, different scenario.
Write a class OutOfStockError that extends Error and sets this.name = "OutOfStockError". Write purchase(item, stock, quantity) that throws an OutOfStockError with the message "Not enough " + item + " in stock" when quantity > stock, and otherwise returns "Purchased " + quantity + " " + item. Check with purchase("keyboards", 3, 5) (should throw, error.name is "OutOfStockError") and purchase("keyboards", 3, 2) (should return "Purchased 2 keyboards").
12. Distinguish between two custom errors.
Write two classes, InsufficientFundsError and NegativeAmountError, both extending Error with their name set appropriately. Write processPayment(amount, balance): throw NegativeAmountError if amount < 0, throw InsufficientFundsError if amount > balance, otherwise return balance - amount. Then write tryPayment(amount, balance) that calls processPayment in a try/catch, uses instanceof to print a different message for each error type, and rethrows anything that’s neither. Check with tryPayment(20, 50) (succeeds), tryPayment(-5, 50) (negative amount message), and tryPayment(100, 50) (insufficient funds message).
13. Debug a missing name.
class TimeoutError extends Error {
constructor(message) {
super(message);
}
}
console.log(new TimeoutError("too slow").name);
Run this first: it logs "Error", not "TimeoutError", because the constructor never sets this.name. Fix the class. Check: after the fix, new TimeoutError("too slow").name should log "TimeoutError".
14. Put it all together.
Write a class ParseError that extends Error and sets this.name = "ParseError". Write parseFile(json) that:
- Uses
try/catch/finally - Inside
try, parsesjsonwithJSON.parseand returns the result - In
catch, builds anew ParseError("Could not parse file: " + error.message), logs itsnameandmessagewithconsole.error(), and returnsnull - In
finally, logs"parseFile finished", regardless of success or failure
Check with parseFile('{"id": 1}') (returns the parsed object, still logs "parseFile finished") and parseFile("not json") (returns null, logs the ParseError’s name and message via console.error(), and still logs "parseFile finished").
Recap
You’ve now practiced catching errors without crashing your program, cleaning up with finally regardless of outcome, raising your own errors with throw, and building custom error classes that a caller can recognize with instanceof. That’s the complete error-handling toolkit for this course.
Module 9, the Checkpoint Project, pulls everything from every module, including this one, into a single program you build yourself.