Error Handling
Objectives
By the end of this lesson, you should be able to:
- Catch an error with
try/catch/finally - Throw a meaningful error using
Errorand a custom subclass - Explain what happens if an error is never caught
💡 Why this matters: This lesson is a language-level review, using
try/catchandErrorcorrectly in plain JavaScript. Module 8 builds on this directly, covering how Express specifically expects errors to be reported and handled.
⚠️ A note on verification: every snippet and output in this lesson was actually run with Node.js.
try, catch, and finally
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function createUser(name) {
if (!name) {
throw new ValidationError("name is required");
}
return { name };
}
try {
createUser("");
} catch (err) {
console.log(err.name, "-", err.message);
}
ValidationError - name is required
throw raises an error, immediately stopping normal execution. try wraps code that might throw, catch (err) runs if it does, receiving the thrown error as err. ValidationError extends Error is exactly the class inheritance pattern from the previous lesson, super(message) calls Error’s own constructor, and this.name = "ValidationError" gives it a distinct, recognizable type.
finally Always Runs
try {
createUser("Sam");
console.log("created successfully");
} catch (err) {
console.log("this should not run");
} finally {
console.log("done attempting to create user");
}
created successfully
done attempting to create user
finally runs whether the try block succeeded or threw, useful for cleanup code that has to happen either way. Here, createUser("Sam") succeeds (a non-empty name), so catch is skipped entirely, but finally still runs afterward.
What Happens If an Error Isn’t Caught
An error thrown with no surrounding try/catch propagates upward, out of the function that threw it, out of whatever called that function, and so on, until either something catches it or it reaches the top of the program, crashing it entirely with a stack trace printed to the console. This is exactly why Module 8 matters so much for Express specifically, an uncaught error inside a route handler can crash an entire running server, not just fail one request, unless it’s handled correctly.
Creating Meaningful Errors
throw new Error("something went wrong") works, but a plain Error doesn’t distinguish what kind of problem occurred. Subclassing Error (like ValidationError above) lets calling code check err.name or use err instanceof ValidationError to react differently depending on what actually went wrong, a validation failure probably deserves a different response than a database connection failure, custom error types make that distinction possible.
Try It
- Write a function
divide(a, b)that throws a plainErrorifbis0, otherwise returnsa / b. Call it inside atry/catchwithbas0, and log the caught error’s message. - Create a custom error class
NotFoundError extends Error, and throw it from a function that looks up a user by id in a small array, when no match is found. - Add a
finallyblock to yourtry/catchfrom question 2 that logs"lookup attempt finished"regardless of whether the user was found. - Explain, in your own words, what happens to a Node.js program if an error is thrown and never caught anywhere.
Recap
trywraps code that might throw,catch (err)handles the thrown error,finallyalways runs afterward, success or failure.throw new Error(message)raises an error, subclassingErrorcreates a distinct, recognizable error type.- An uncaught error propagates upward until something catches it, or it crashes the program.
Next lesson: promises, representing a value that isn’t ready yet.