Error Handling

Course 2 · Ch 5
Error Handling: try/catch/finally and Custom Error Classes
Fundamentals Chapter 10 used try/catch around fetch — this chapter covers the full mechanism, including how to throw and design your own errors

Fundamentals Chapter 10 wrapped await fetch(...) in try/catch without explaining the mechanism fully. This chapter covers throw, the complete try/catch/finally structure, and — using Chapter 3's class/extends syntax — how to build custom error types carrying more information than a plain message string.

throw — Raising an Error Deliberately

function divide(a, b) { if (b === 0) { throw new Error("Cannot divide by zero"); } return a / b; } divide(10, 0); // Uncaught Error: Cannot divide by zero — stops execution entirely

throw new Error("message") immediately stops the current function (and everything that called it) from continuing normally — unlike Go's error return values (a deliberate contrast worth knowing if the Go course is ever revisited), JavaScript errors propagate upward automatically until something explicitly catches them.

try/catch — Catching a Thrown Error

try { const result = divide(10, 0); console.log(result); // never runs — divide already threw } catch (error) { console.log("Something went wrong:", error.message); } // "Something went wrong: Cannot divide by zero"

Code inside try { } runs normally until something throws — at that exact point, execution jumps straight into catch (error) { }, skipping anything remaining in the try block. error.message holds the text passed to new Error(...); every error also has a name property ("Error" by default).

finally — Runs No Matter What

function loadData() { console.log("Loading started"); try { throw new Error("Network failure"); } catch (error) { console.log("Caught:", error.message); } finally { console.log("Loading finished"); // runs whether or not an error was thrown } }

finally { } runs after try/catch complete, regardless of whether an error occurred — useful for cleanup work (hiding a loading spinner, closing a connection) that needs to happen either way. This is conceptually similar to Go's defer from the Go Intermediate course, just scoped specifically to error handling rather than every function exit.

Custom Error Classes

class ValidationError extends Error { constructor(message, field) { super(message); // sets up the standard message/stack behaviour this.name = "ValidationError"; this.field = field; } } function validateAge(age) { if (age < 0) { throw new ValidationError("Age cannot be negative", "age"); } }

class ValidationError extends Error (Chapter 3's inheritance, applied to the built-in Error type) creates a genuinely new error type carrying extra structured data — here, field — alongside the standard message. super(message) must run first, exactly as with any other subclass constructor, to set up Error's own internal behaviour (including the stack trace).

Distinguishing Error Types in a catch Block

try { validateAge(-5); } catch (error) { if (error instanceof ValidationError) { console.log(`Invalid field "${error.field}": ${error.message}`); } else { console.log("Unexpected error:", error.message); } } // Invalid field "age": Age cannot be negative

error instanceof ValidationError checks whether the caught error is specifically that custom type (or a subclass of it) — this is JavaScript's rough equivalent of Go's errors.As from the Go Advanced course, letting a catch block react differently depending on exactly what went wrong, rather than treating every error identically.

A catch block with no parameter catches everything indiscriminately
Without checking instanceof, a single catch block treats a genuine validation problem the same as an unrelated bug (a typo causing a TypeError, for instance) — both get silently swallowed and handled identically. Checking the specific error type before deciding how to respond avoids masking real bugs as if they were expected validation failures.
PiecePurpose
throw new Error("msg")Deliberately raise an error, halting normal execution
try { } catch (e) { }Run code; if it throws, jump to catch instead of crashing
finally { }Always runs after try/catch, error or not — for cleanup
class X extends ErrorDefine a custom error type with extra data fields
error instanceof XCheck which specific error type was actually caught

Coding Challenges

Challenge 1

Write a function parsePositiveNumber(value) that throws a plain Error if value is negative or not a number (use isNaN), otherwise returns it. Call it inside a try/catch with a value that triggers the error, logging the caught message.

📄 View solution
Challenge 2

Define a custom error class InsufficientFundsError extending Error, with a constructor taking (message, shortfall) and storing shortfall. Write a function withdraw(balance, amount) that throws it if amount > balance, including the shortfall (amount - balance). Catch it and log a message using error.shortfall.

📄 View solution
Challenge 3

Write a function riskyOperation(shouldFail) that throws a RangeError if shouldFail is true, otherwise returns "Success". Wrap a call in try/catch/finally: log the result or the error's name+message in catch, and always log "Operation complete" in finally, regardless of outcome. Call it twice, once with each boolean.

📄 View solution

Chapter 5 Quick Reference

  • throw new Error("message") — deliberately raises an error, halting normal flow
  • try { } catch (error) { } — catches a thrown error instead of letting it crash the program
  • finally { } — always runs after try/catch, error or not
  • error.message — the text passed when the error was created; error.name — its type name
  • class X extends Error — custom error type; must call super(message) first
  • error instanceof X — checks the specific error type caught, for differentiated handling
  • Built-in error types: Error, TypeError, RangeError, SyntaxError, and more
  • Next chapter: array/object copying — shallow vs deep, and common mutation pitfalls