Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 121
Intermediate

JavaScript Error Handling

Handle errors gracefully in JavaScript using try/catch, custom error classes, and finally blocks.

TL;DR

  1. 01Wrap risky code in try/catch to handle thrown errors cleanly.
  2. 02Throw custom error classes to make catch blocks more precise.
  3. 03Use finally to run cleanup code regardless of success or failure.

Tips

  1. 01Create custom error classes to identify error types in catch blocks — makes branching logic far clearer than checking messages.
  2. 02Use finally blocks to release resources like file handles or database connections, since they run regardless of errors.
  3. 03Re-throw an error after logging it so calling code further up the stack still gets a chance to handle it.

Warnings

  1. 01Never swallow errors silently with an empty catch block — always log or handle them so bugs don't disappear.
  2. 02Throwing a plain string instead of an Error object loses the automatic stack trace, making bugs harder to track down.
  3. 03A return statement inside finally silently overrides any return or thrown error from the try or catch block above it.
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 122
Intermediate

JavaScript Error Handling

(continued)

Try/Catch Basics

  • try/catch

    Wrap risky code in a try block to intercept runtime errors.

    try {
      const result = riskyOperation();
      console.log(result);
    } catch (error) {
      console.error('Error:', error.message);
    }
  • Catch only runs on error

    The catch block only runs when an error is thrown in try.

    try {
      const data = JSON.parse('invalid');
    } catch (error) {
      console.error('Caught:', error.message); // SyntaxError
    }
  • Error object properties

    The error object contains a message, name, and a stack trace.

    catch (error) {
      console.log(error.name);    // "SyntaxError"
      console.log(error.message); // "Unexpected token i"
      console.log(error.stack);   // full trace
    }
  • Execution stops at throw

    Code inside try after the thrown line does not execute.

    try {
      throw new Error('stop here');
      console.log('never runs');
    } catch (e) {
      console.log(e.message); // "stop here"
    }
  • Optional catch binding

    Omit the catch binding if you don't need the error object.

    try {
      mayFail();
    } catch {
      // optional binding — no variable needed
      console.log('Something went wrong');
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 123
Intermediate

JavaScript Error Handling

(continued)

Finally Block

  • Always runs

    Run cleanup code with finally, which always executes.

    try {
      const file = openFile('data.txt');
      processFile(file);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      closeFile(); // Always runs
    }
  • Runs without an error

    Finally runs even when there is no error in try.

  • Runs before return

    Finally runs even if catch re-throws or the try block returns early.

    function getData() {
      try {
        return fetchData();
      } finally {
        cleanup(); // runs before function returns
      }
    }
  • Releasing resources

    Use finally to release resources like connections or file handles.

    let connection;
    try {
      connection = openDB();
      return connection.query('SELECT * FROM users');
    } finally {
      connection?.close();
    }
  • Resetting UI state

    Finally is useful for resetting loading or spinner state in UIs.

    setLoading(true);
    try {
      await fetchData();
    } finally {
      setLoading(false); // runs on success or failure
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 124
Intermediate

JavaScript Error Handling

(continued)

Throwing Errors

  • throw new Error()

    Throw a new Error with a descriptive message.

    function divide(a, b) {
      if (b === 0) {
        throw new Error('Division by zero');
      }
      return a / b;
    }
  • Error objects, not strings

    You can throw any value, but Error objects are best practice.

    // Avoid: throw 'something went wrong';
    // Prefer: throw new Error('something went wrong');
  • Built-in error types

    Throw built-in error types for more specific problems.

    function setAge(age) {
      if (typeof age !== 'number') {
        throw new TypeError('Age must be a number');
      }
      if (age < 0 || age > 150) {
        throw new RangeError('Age out of valid range');
      }
    }
  • Re-throwing

    Re-throw errors after logging to let upstream code handle them.

    try {
      riskyOp();
    } catch (e) {
      logger.error(e);
      throw e; // propagate to caller
    }
  • Throwing inside catch

    Throwing inside a catch block escalates the error upstream.

    catch (error) {
      if (error instanceof SyntaxError) {
        throw new Error('Config file is malformed');
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 125
Intermediate

JavaScript Error Handling

(continued)

Custom Error Classes

  • Extending Error

    Create custom error types by extending the built-in Error class.

    class ValidationError extends Error {
      constructor(message) {
        super(message);
        this.name = 'ValidationError';
      }
    }
  • instanceof checks

    Check error type with instanceof in catch blocks.

    try {
      if (!email.includes('@')) {
        throw new ValidationError('Invalid email');
      }
    } catch (error) {
      if (error instanceof ValidationError) {
        console.log('Validation error:', error.message);
      }
    }
  • Extra properties

    Add extra properties to custom errors for richer context.

    class HttpError extends Error {
      constructor(status, message) {
        super(message);
        this.name = 'HttpError';
        this.status = status;
      }
    }
    throw new HttpError(404, 'Resource not found');
  • Multiple error classes

    Use multiple custom error classes to categorize problems.

    class NetworkError extends Error { }
    class AuthError extends Error { }
    class NotFoundError extends Error { }
  • Branching in catch

    Handle specific error types separately, letting unknown errors bubble up.

    catch (error) {
      if (error instanceof AuthError) return redirectToLogin();
      if (error instanceof NetworkError) return showRetry();
      throw error; // unknown errors bubble up
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 126
Intermediate

JavaScript Error Handling

(continued)

Common Error Types

  • SyntaxError

    Occurs when code or data cannot be parsed.

    try {
      JSON.parse('invalid json');
    } catch (error) {
      if (error instanceof SyntaxError) {
        console.log('Invalid JSON format');
      }
    }
  • TypeError

    Occurs when a value is used with the wrong type.

    try {
      const x = null;
      x.method(); // TypeError: Cannot read properties of null
    } catch (e) {
      console.log(e instanceof TypeError); // true
    }
  • ReferenceError

    Occurs when a variable is not defined.

    try {
      console.log(undeclaredVar);
    } catch (e) {
      console.log(e instanceof ReferenceError); // true
    }
  • RangeError

    Occurs when a number falls outside valid bounds.

    try {
      new Array(-1); // RangeError: Invalid array length
    } catch (e) {
      console.log(e instanceof RangeError); // true
    }
  • error.name

    Check error names as a string alternative to instanceof.

    catch (error) {
      console.log(error.name); // "TypeError", "RangeError", etc.
      if (error.name === 'TypeError') handleTypeError(error);
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 127
Intermediate

JavaScript Error Handling

(FAQ)

FAQ

Yes — wrap your await call inside a try/catch block and it will catch rejected promises just like synchronous errors. Alternatively, chain .catch() on the promise, but try/catch keeps async error handling visually consistent with synchronous code.

Yes, finally always executes before the function actually returns, even if try or catch contains a return. Be careful not to place a return inside finally itself. It overrides any return value from try or catch.

Always throw an Error object (or a subclass), never a plain string. Error objects capture a stack trace automatically, which is essential for debugging. Thrown strings produce no stack trace and are much harder to track down.

Use instanceof to branch on the error's class: if (err instanceof ValidationError) handles it differently from if (err instanceof NetworkError). This is why custom error classes are worth defining. Checking err.message with string matching is fragile and breaks as messages change.

A ReferenceError means you accessed a variable that doesn't exist in scope. A TypeError means a value exists but you're using it in an incompatible way, like calling null as a function. Recognizing which one you have narrows down the likely cause of the bug.

Useful Cheatsheetsusefulcheatsheets.com
JavaScript Error Handling
Chapter 15 · Page 128
Intermediate

JavaScript Error Handling

(In Practice)
In Practice

Validating and Fetching User Data Safely

Combines a custom ValidationError class, try/catch/finally, and instanceof checks to handle distinct failure modes cleanly.

  1. 01ValidationError extends Error so it carries a stack trace and can be caught with instanceof.
  2. 02Invalid input throws before the fetch even starts, keeping validation separate from network errors.
  3. 03The catch block branches on instanceof to give validation and network failures different handling.
  4. 04finally always resets the loading state, whether the request succeeded, failed, or never started.
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError';
  }
}

async function getUser(id) {
  if (typeof id !== 'number') {
    throw new ValidationError('id must be a number');
  }

  setLoading(true);
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`Request failed: ${res.status}`);
    return await res.json();
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error('Bad input:', error.message);
    } else {
      console.error('Fetch failed:', error.message);
    }
    return null;
  } finally {
    setLoading(false);
  }
}
Takeaway

Custom error classes plus instanceof checks let one catch block handle different failure types distinctly.