CodingNic

Modern JavaScript for Node.js

Error Handling

Modern JavaScript for Node.js 10 min read

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 Error and a custom subclass
  • Explain what happens if an error is never caught

💡 Why this matters: This lesson is a language-level review, using try/catch and Error correctly 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

javascript
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);
}
text
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

javascript
try {
  createUser("Sam");
  console.log("created successfully");
} catch (err) {
  console.log("this should not run");
} finally {
  console.log("done attempting to create user");
}
text
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

  1. Write a function divide(a, b) that throws a plain Error if b is 0, otherwise returns a / b. Call it inside a try/catch with b as 0, and log the caught error’s message.
  2. 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.
  3. Add a finally block to your try/catch from question 2 that logs "lookup attempt finished" regardless of whether the user was found.
  4. Explain, in your own words, what happens to a Node.js program if an error is thrown and never caught anywhere.

Recap

  • try wraps code that might throw, catch (err) handles the thrown error, finally always runs afterward, success or failure.
  • throw new Error(message) raises an error, subclassing Error creates 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.