|

JavaScript 39 🧬 Error handling — try/catch/finally/throw

try {
  JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
} finally {
  console.log('Done');
}

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

try {
  console.log(divide(10, 2));
  console.log(divide(10, 0));
} catch (err) {
  console.log('Error:', err.message);
}

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

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log('Validation:', err.message);
  } else if (err instanceof Error) {
    console.log('Generic:', err.message);
  }
}

Errors are a normal part of every program — files go missing, networks drop, users type nonsense, and code has bugs. JavaScript gives you a structured way to throw, catch, and handle errors so a single failure doesn’t crash the whole app. The try / catch / finally block is the foundation of that system.

Key point: When something goes wrong, JavaScript throws an error. If nothing catches it, the error propagates up the call stack and eventually terminates the program. try / catch lets you intercept errors, decide what to do, and keep running.


a – What is an Error

An Error in JavaScript is an object, not a string. When something fails, an object is created with useful information.

Properties of an Error:

PropertyMeaning
nameType of error ("Error", "TypeError", "SyntaxError")
messageHuman-readable description
stackStack trace showing where it happened
causeOptional underlying error (ES2022)

Creating an error:

const err = new Error('Something went wrong');
console.log(err.name);     // Error
console.log(err.message);  // Something went wrong
console.log(err.stack);    // multi-line stack trace

Built-in error types:

TypeWhen it occurs
ErrorGeneric base error
TypeErrorWrong type — calling a non-function, reading undefined.foo
ReferenceErrorUsing an undefined variable
SyntaxErrorInvalid code — usually caught at parse time
RangeErrorNumber out of valid range
URIErrorMalformed URI
EvalErrorLegacy, rarely used

Examples:

try {
  undefinedFunction();
} catch (err) {
  console.log(err.name);     // ReferenceError
  console.log(err.message);  // undefinedFunction is not defined
}

try {
  null.foo;
} catch (err) {
  console.log(err.name);     // TypeError
}

try {
  new Array(-1);
} catch (err) {
  console.log(err.name);     // RangeError
}

Errors are objects, not strings:

// ❌ Old style — throws a string
throw 'something broke';

// ✅ Modern — throws an Error object
throw new Error('something broke');

Throwing strings is legal but bad practice — you lose .name, .stack, and type checking. Always throw Error objects.

Why errors matter:

  • Fail loudly — silent failures are the worst bugs
  • Preserve context.stack tells you where
  • Keep programs running — catch and recover
  • Distinguish cases — custom error classes make handling precise

b – try, catch, and finally

The try / catch / finally structure is how you handle errors.

Syntax:

try {
  // code that might throw
} catch (error) {
  // runs if an error is thrown
} finally {
  // always runs — success or failure
}

The try block:

Holds the code that might throw. If anything inside throws, execution jumps to catch.

try {
  const data = JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
}
// Caught: Expected property name or '}' in JSON at position 2

The catch block:

Receives the error object. Runs only if something in try throws.

try {
  riskyOperation();
} catch (error) {
  console.error(error.name, error.message);
}

You can omit the binding in modern JS if you don’t need the error object:

try {
  riskyOperation();
} catch {
  console.log('Something failed');
}

The finally block:

Runs whether or not an error occurred. Used for cleanup — closing files, releasing locks, hiding spinners.

try {
  showSpinner();
  const data = await fetchData();
  return data;
} catch (err) {
  console.error('Failed:', err);
  return null;
} finally {
  hideSpinner();  // always runs
}

Execution flow:

┌──────────────────────────────────────────────┐
│           try / catch / finally              │
│                                              │
│  try {                                       │
│    // risky code                             │
│    ──── success ────►  skip catch            │
│    ──── throw ──────►  run catch             │
│  }                                           │
│                                              │
│  catch (err) {                               │
│    // handle error                           │
│  }                                           │
│                                              │
│  finally {                                   │
│    // ALWAYS runs (cleanup)                  │
│  }                                           │
│                                              │
└──────────────────────────────────────────────┘

finally runs even if try returns:

function test() {
  try {
    return 'from try';
  } finally {
    console.log('finally runs!');
  }
}

console.log(test());
// [ finally runs! ]
// [ from try ]

The return in try doesn’t skip finally.

finally overrides try and catch returns:

function test() {
  try {
    return 'try';
  } finally {
    return 'finally';
  }
}

console.log(test());
// [ finally ]

If finally has a return, it overrides everything. Avoid this — it’s confusing.

A practical example — file-like cleanup:

function processFile() {
  console.log('Opening file');
  try {
    console.log('Processing...');
    throw new Error('Corrupted data');
  } catch (err) {
    console.log('Error:', err.message);
  } finally {
    console.log('Closing file');
  }
}

processFile();
// [ Opening file ]
// [ Processing... ]
// [ Error: Corrupted data ]
// [ Closing file ]

The file gets closed no matter what.

Full example:

try {
  JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
} finally {
  console.log('Done');
}
// [ Caught: Expected property name or '}' in JSON at position 2 ]
// [ Done ]

c – Throwing errors and custom error classes

You don’t have to wait for JavaScript to throw — you can throw errors yourself, and define your own error types.

Throwing errors with throw:

function divide(a, b) {
  if (b === 0) {
    throw new Error('Cannot divide by zero');
  }
  return a / b;
}

console.log(divide(10, 2));
// [ 5 ]

try {
  divide(10, 0);
} catch (err) {
  console.log(err.message);
}
// [ Cannot divide by zero ]

throw immediately stops execution of the current function and propagates the error up the stack.

Throwing values of any type:

throw new Error('real error');   // ✅ best practice
throw 'a string';                // ⚠️ works, but loses info
throw { code: 404 };             // ⚠️ works, but not an Error
throw 42;                        // ⚠️ works, but terrible

Only Error objects have .name, .message, .stack. Anything else is just a value.

Custom error classes — the modern pattern:

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

class NetworkError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.name = 'NetworkError';
    this.statusCode = statusCode;
  }
}

extends Error inherits everything. Setting this.name makes err.name correct (otherwise it would be 'Error').

Using custom errors:

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log('Validation problem:', err.message);
  } else if (err instanceof NetworkError) {
    console.log('Network problem:', err.message);
  } else if (err instanceof Error) {
    console.log('Unknown error:', err.message);
  }
}
// [ Validation problem: Invalid email ]

Order matters — check the most specific class first, then the base.

Adding extra properties:

class HTTPError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'HTTPError';
    this.status = status;
  }
}

try {
  throw new HTTPError('Not found', 404);
} catch (err) {
  if (err instanceof HTTPError) {
    console.log(`${err.status}: ${err.message}`);
  }
}
// [ 404: Not found ]

Error cause — chaining (ES2022):

try {
  JSON.parse('{ bad }');
} catch (err) {
  throw new Error('Config load failed', { cause: err });
}

The cause option preserves the original error:

try {
  throw new Error('Outer', { cause: new Error('Inner') });
} catch (err) {
  console.log(err.message);        // Outer
  console.log(err.cause.message);  // Inner
}

Useful for wrapping low-level errors with higher-level context.

Re-throwing errors:

Sometimes you want to log an error but not swallow it:

function loadData() {
  try {
    return JSON.parse(someInput);
  } catch (err) {
    console.error('Debug: failed to parse', err);
    throw err;   // re-throw — let the caller decide
  }
}

Full example:

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

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log('Validation:', err.message);
  } else if (err instanceof Error) {
    console.log('Generic:', err.message);
  }
}
// [ Validation: Invalid email ]

Complete Example Session

// ============================================
// PART 1: BASIC ERROR OBJECT
// ============================================

const err = new Error('Something went wrong');

console.log(err.name);
// [ Error ]

console.log(err.message);
// [ Something went wrong ]

console.log(typeof err.stack);
// [ string ]

// ============================================
// PART 2: BUILT-IN ERROR TYPES
// ============================================

try { undefinedFunction(); }
catch (e) { console.log(e.name); }
// [ ReferenceError ]

try { null.foo; }
catch (e) { console.log(e.name); }
// [ TypeError ]

try { new Array(-1); }
catch (e) { console.log(e.name); }
// [ RangeError ]

// ============================================
// PART 3: TRY / CATCH
// ============================================

try {
  JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
}
// [ Caught: Expected property name or '}' in JSON at position 2 ]

// ============================================
// PART 4: TRY / CATCH / FINALLY
// ============================================

try {
  JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
} finally {
  console.log('Done');
}
// [ Caught: Expected property name or '}' in JSON at position 2 ]
// [ Done ]

// ============================================
// PART 5: FINALLY RUNS ON SUCCESS TOO
// ============================================

try {
  JSON.parse('{"ok": true}');
} catch (err) {
  console.log('Never runs');
} finally {
  console.log('Always runs');
}
// [ Always runs ]

// ============================================
// PART 6: THROW
// ============================================

function divide(a, b) {
  if (b === 0) {
    throw new Error('Cannot divide by zero');
  }
  return a / b;
}

console.log(divide(10, 2));
// [ 5 ]

try {
  divide(10, 0);
} catch (err) {
  console.log('Error:', err.message);
}
// [ Error: Cannot divide by zero ]

// ============================================
// PART 7: FINALLY WITH RETURN
// ============================================

function test1() {
  try {
    return 'from try';
  } finally {
    console.log('finally runs');
  }
}

console.log(test1());
// [ finally runs ]
// [ from try ]

// ============================================
// PART 8: FINALLY OVERRIDES RETURN
// ============================================

function test2() {
  try {
    return 'try';
  } finally {
    return 'finally';
  }
}

console.log(test2());
// [ finally ]

// ============================================
// PART 9: CUSTOM ERROR CLASS
// ============================================

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

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  console.log(err.name);
  // [ ValidationError ]
  console.log(err.message);
  // [ Invalid email ]
}

// ============================================
// PART 10: MULTIPLE CUSTOM ERRORS
// ============================================

class NetworkError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.name = 'NetworkError';
    this.statusCode = statusCode;
  }
}

function handle(err) {
  if (err instanceof ValidationError) return 'validation';
  if (err instanceof NetworkError) return 'network';
  if (err instanceof Error) return 'generic';
  return 'unknown';
}

console.log(handle(new ValidationError('x')));
// [ validation ]

console.log(handle(new NetworkError('y', 500)));
// [ network ]

console.log(handle(new Error('z')));
// [ generic ]

// ============================================
// PART 11: ORDER MATTERS
// ============================================

class DogError extends Error {}
class AnimalError extends Error {}

try {
  throw new DogError('woof');
} catch (err) {
  if (err instanceof AnimalError) {
    console.log('animal');
  } else if (err instanceof DogError) {
    console.log('dog');       // never reached
  }
}
// (nothing printed)

// ============================================
// PART 12: ERROR CAUSE (ES2022)
// ============================================

try {
  throw new Error('Outer', { cause: new Error('Inner') });
} catch (err) {
  console.log(err.message);
  // [ Outer ]
  console.log(err.cause.message);
  // [ Inner ]
}

// ============================================
// PART 13: RE-THROW
// ============================================

function loadData() {
  try {
    return JSON.parse('{ bad }');
  } catch (err) {
    console.error('Failed to parse');
    throw err;
  }
}

try {
  loadData();
} catch (err) {
  console.log('Outer caught:', err.name);
}
// [ Failed to parse ]
// [ Outer caught: SyntaxError ]

// ============================================
// PART 14: CATCH WITHOUT BINDING
// ============================================

try {
  JSON.parse('{ bad }');
} catch {
  console.log('Failed');
}
// [ Failed ]

// ============================================
// PART 15: VALIDATION
// ============================================

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 range');
  }
  return age;
}

try {
  setAge('30');
} catch (err) {
  console.log(err.name, '-', err.message);
}
// [ TypeError - Age must be a number ]

try {
  setAge(200);
} catch (err) {
  console.log(err.name, '-', err.message);
}
// [ RangeError - Age out of range ]

// ============================================
// PART 16: ASYNC ERRORS WITH TRY/CATCH
// ============================================

async function fetchData() {
  throw new Error('Network failed');
}

async function main() {
  try {
    await fetchData();
  } catch (err) {
    console.log('Async caught:', err.message);
  }
}

main();
// [ Async caught: Network failed ]

// ============================================
// PART 17: CLEANUP PATTERN
// ============================================

function useResource() {
  console.log('Acquire');
  try {
    console.log('Use');
    throw new Error('boom');
  } catch (err) {
    console.log('Handle:', err.message);
  } finally {
    console.log('Release');
  }
}

useResource();
// [ Acquire ]
// [ Use ]
// [ Handle: boom ]
// [ Release ]

// ============================================
// PART 18: NESTED TRY/CATCH
// ============================================

try {
  try {
    throw new Error('inner');
  } catch (err) {
    console.log('Inner caught');
    throw new Error('outer');
  } finally {
    console.log('Inner finally');
  }
} catch (err) {
  console.log('Outer caught:', err.message);
}
// [ Inner caught ]
// [ Inner finally ]
// [ Outer caught: outer ]

// ============================================
// PART 19: CUSTOM ERROR WITH PROPERTIES
// ============================================

class HTTPError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'HTTPError';
    this.status = status;
  }
}

try {
  throw new HTTPError('Not Found', 404);
} catch (err) {
  console.log(`${err.status}: ${err.message}`);
}
// [ 404: Not Found ]

// ============================================
// PART 20: FULL SCRIPT
// ============================================

try {
  JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
} finally {
  console.log('Done');
}

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

try {
  console.log(divide(10, 2));
  console.log(divide(10, 0));
} catch (err) {
  console.log('Error:', err.message);
}

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

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log('Validation:', err.message);
  } else if (err instanceof Error) {
    console.log('Generic:', err.message);
  }
}

Quick Reference

try / catch / finally

BlockRuns when
tryAlways — the risky code
catchOnly if try throws
finallyAlways — success or failure

Error Properties

PropertyMeaning
nameType of error
messageDescription
stackStack trace
causeUnderlying error (ES2022)

Built-in Error Types

TypeTypical cause
ErrorBase class
TypeErrorWrong type
ReferenceErrorUndefined variable
SyntaxErrorInvalid syntax
RangeErrorOut of range
URIErrorBad URI

Throwing

SyntaxMeaning
throw new Error('msg')Throw a generic error
throw new TypeError('msg')Throw a specific error
throw new MyError('msg')Throw a custom error
throw errRe-throw

Custom Errors

StepCode
Extend Errorclass MyError extends Error {}
Call supersuper(message)
Set namethis.name = 'MyError'
Add propsthis.code = code

Error Handling Patterns

PatternUse
try/catchHandle errors locally
try/finallyCleanup only
try/catch/finallyHandle + cleanup
catch { }Ignore error object
Re-throwLog and re-throw

Best Practices

Do This:

// Throw Error objects
throw new Error('message');                    // ✅

// Use specific error types
throw new TypeError('must be a number');       // ✅

// Extend Error for custom errors
class MyError extends Error {}                 // ✅

// Set this.name in custom errors
this.name = 'MyError';                         // ✅

// Check specific before general
if (err instanceof MyError) { ... }
else if (err instanceof Error) { ... }         // ✅

// Use finally for cleanup
try { ... } finally { cleanup(); }             // ✅

// Chain errors with cause
throw new Error('Outer', { cause: err });      // ✅

// Re-throw when you can't handle
catch (err) { log(err); throw err; }           // ✅

// Omit catch binding when unused
try { ... } catch { ... }                      // ✅

Don’t Do This:

// Don't throw strings
throw 'error';                                 // ❌

// Don't throw numbers
throw 42;                                      // ❌

// Don't swallow errors silently
try { ... } catch (err) {}                     // ❌

// Don't return from finally
finally { return 'x'; }                        // ❌ overrides

// Don't check general before specific
if (err instanceof Error) { ... }
else if (err instanceof TypeError) { ... }     // ❌ unreachable

// Don't catch and ignore
catch (err) { /* nothing */ }                  // ❌

// Don't use try/catch for control flow
try { parse(x) } catch { fallback() }          // ⚠️  overuse

// Don't forget this.name in custom errors
class E extends Error {}                       // ⚠️  err.name === 'Error'

// Don't use errors for expected conditions
try { findUser() } catch { ... }               // ⚠️  return null instead

Common Pitfalls

PitfallProblemSolution
Throwing stringsNo .name/.stackthrow new Error(...)
Missing this.nameerr.name === 'Error'Set it in the constructor
General before specificBranch unreachableCheck specific first
Silent catchBugs disappearLog or re-throw
return in finallyOverrides try returnAvoid
finally without cleanupPointlessUse only for cleanup
Catch for control flowSlow, hides bugsUse return values
Async error not caughtUnhandled rejectionawait inside try

Real-World Examples

1. Parse JSON Safely

try {
  const data = JSON.parse('{ invalid }');
} catch (err) {
  console.log('Parse failed:', err.message);
}
// [ Parse failed: Expected property name or '}' in JSON at position 2 ]

2. Divide with Guard

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

try {
  console.log(divide(10, 0));
} catch (err) {
  console.log(err.message);
}
// [ Division by zero ]

3. Validate Input

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 range');
  return age;
}

try {
  setAge('30');
} catch (err) {
  console.log(err.name, '-', err.message);
}
// [ TypeError - Age must be a number ]

4. Custom ValidationError

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

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  console.log(err.name);
  // [ ValidationError ]
}

5. HTTP Error with Status

class HTTPError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'HTTPError';
    this.status = status;
  }
}

try {
  throw new HTTPError('Not Found', 404);
} catch (err) {
  console.log(`${err.status}: ${err.message}`);
}
// [ 404: Not Found ]

6. Cleanup with finally

function useResource() {
  console.log('Acquire');
  try {
    console.log('Use');
    throw new Error('boom');
  } catch (err) {
    console.log('Handle:', err.message);
  } finally {
    console.log('Release');
  }
}

useResource();
// [ Acquire ]
// [ Use ]
// [ Handle: boom ]
// [ Release ]

7. Async with try/catch

async function fetchData() {
  throw new Error('Network failed');
}

async function main() {
  try {
    await fetchData();
  } catch (err) {
    console.log('Async caught:', err.message);
  }
}

main();
// [ Async caught: Network failed ]

8. Error Cause

try {
  throw new Error('Config failed', { cause: new Error('File missing') });
} catch (err) {
  console.log(err.message);        // Config failed
  console.log(err.cause.message);  // File missing
}

9. Re-throw

function load() {
  try {
    return JSON.parse('{ bad }');
  } catch (err) {
    console.error('Parse failed');
    throw err;
  }
}

try {
  load();
} catch (err) {
  console.log('Outer:', err.name);
}
// [ Parse failed ]
// [ Outer: SyntaxError ]

10. Retry Logic

async function retry(fn, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === attempts - 1) throw err;
      console.log(`Attempt ${i + 1} failed, retrying...`);
    }
  }
}

retry(async () => {
  throw new Error('always fails');
}, 2).catch(err => console.log('Gave up:', err.message));
// [ Attempt 1 failed, retrying... ]
// [ Gave up: always fails ]

11. Fallback Value

function parseConfig(text) {
  try {
    return JSON.parse(text);
  } catch {
    return {};
  }
}

console.log(parseConfig('{ bad }'));
// [ {} ]

12. Distinguish Error Types

function handle(err) {
  if (err instanceof TypeError) return 'type';
  if (err instanceof RangeError) return 'range';
  if (err instanceof Error) return 'generic';
  return 'unknown';
}

console.log(handle(new TypeError('x')));
// [ type ]
console.log(handle(new Error('y')));
// [ generic ]

13. Nested try

try {
  try {
    throw new Error('inner');
  } catch (err) {
    console.log('Inner caught');
    throw new Error('outer');
  } finally {
    console.log('Inner finally');
  }
} catch (err) {
  console.log('Outer caught:', err.message);
}
// [ Inner caught ]
// [ Inner finally ]
// [ Outer caught: outer ]

14. Assert-style Check

function assert(condition, message) {
  if (!condition) throw new Error(message);
}

try {
  assert(1 === 2, 'Math is broken');
} catch (err) {
  console.log(err.message);
}
// [ Math is broken ]

15. Timeout Wrapper

function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), ms)
    )
  ]);
}

16. Graceful Degradation

function loadUser(id) {
  try {
    return api.fetchUser(id);
  } catch (err) {
    console.error('Falling back to cache');
    return cache.get(id);
  }
}

17. Error Logging

function logError(err) {
  console.error(`[${new Date().toISOString()}] ${err.name}: ${err.message}`);
  if (err.stack) console.error(err.stack);
}

try {
  JSON.parse('{ bad }');
} catch (err) {
  logError(err);
}

18. Try/Finally Without Catch

function cleanup() {
  console.log('cleanup');
}

try {
  console.log('work');
} finally {
  cleanup();
}
// [ work ]
// [ cleanup ]

19. Error in Finally

try {
  throw new Error('original');
} finally {
  throw new Error('from finally');
}
// Uncaught Error: from finally

The error thrown in finally replaces the original.

20. Full Script

try {
  JSON.parse('{ invalid }');
} catch (err) {
  console.log('Caught:', err.message);
} finally {
  console.log('Done');
}

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

try {
  console.log(divide(10, 2));
  console.log(divide(10, 0));
} catch (err) {
  console.log('Error:', err.message);
}

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

try {
  throw new ValidationError('Invalid email');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log('Validation:', err.message);
  } else if (err instanceof Error) {
    console.log('Generic:', err.message);
  }
}

Visual: Error Flow

┌──────────────────────────────────────────────┐
│           Normal execution                   │
│                                              │
│  try                                         │
│    │                                         │
│    ▼                                         │
│  statement 1  ✓                              │
│  statement 2  ✓                              │
│  statement 3  ✓                              │
│    │                                         │
│    ▼                                         │
│  finally                                     │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│           Error thrown                       │
│                                              │
│  try                                         │
│    │                                         │
│    ▼                                         │
│  statement 1  ✓                              │
│  statement 2  ✗ throw                        │
│    │                                         │
│    ├──► catch (err)                          │
│    │    handle error                         │
│    │                                         │
│    ▼                                         │
│  finally                                     │
│                                              │
└──────────────────────────────────────────────┘

Visual: try / catch / finally

┌──────────────────────────────────────────────┐
│  ┌────────────────────────────────────────┐  │
│  │  try                                   │  │
│  │    // risky code                       │  │
│  └───────────────┬────────────────────────┘  │
│                  │                           │
│         ┌────────┴────────┐                  │
│      success            throw                │
│         │                 │                  │
│         │                 ▼                  │
│         │       ┌───────────────────────┐    │
│         │       │  catch (err)          │    │
│         │       │    // handle          │    │
│         │       └───────────┬───────────┘    │
│         │                   │                │
│         └────────┬──────────┘                │
│                  │                           │
│                  ▼                           │
│       ┌─────────────────────────┐            │
│       │  finally                │            │
│       │    // always runs       │            │
│       └─────────────────────────┘            │
│                                              │
└──────────────────────────────────────────────┘

Visual: Error Class Hierarchy

┌──────────────────────────────────────────────┐
│  Error                                       │
│    │                                         │
│    ├── TypeError                             │
│    ├── ReferenceError                        │
│    ├── SyntaxError                           │
│    ├── RangeError                            │
│    ├── URIError                              │
│    └── (your custom errors)                  │
│         │                                    │
│         ├── ValidationError                  │
│         ├── NetworkError                     │
│         └── HTTPError                        │
│                                              │
└──────────────────────────────────────────────┘

Summary

ConceptSyntaxExample
Throwthrow new Error('msg')throw new Error('fail')
Trytry { ... }try { risky() }
Catchcatch (err) { ... }catch (err) { log(err) }
Finallyfinally { ... }finally { cleanup() }
Catch no bindingcatch { ... }catch { log('fail') }
Error propserr.name, err.messageerr.message
Stackerr.stackPrint for debug
Cause{ cause: err }new Error('x', { cause: e })
Custom errorclass E extends Errorclass MyError extends Error
Set namethis.name = 'MyError'In constructor
Type checkerr instanceof TypeErrorSpecific before general
Re-throwthrow errIn catch
Built-insError, TypeError, RangeErrorthrow new TypeError()

Key takeaways:

  • Error is an object — always throw Error instances, never strings
  • try holds risky code; catch handles errors; finally always runs
  • finally is for cleanup — closing files, releasing resources, hiding UI
  • finally runs even when try returns — and its return overrides
  • Built-in errors: Error, TypeError, ReferenceError, SyntaxError, RangeError, URIError
  • Custom errors extend Error, call super(message), and set this.name
  • Check specific before generalinstanceof MyError before instanceof Error
  • Use { cause: err } to chain errors with context
  • Re-throw with throw err when you can’t handle — never swallow silently
  • Async errors inside await are caught by try/catch just like sync errors
  • Errors are for exceptional cases — not for control flow

Remember: Errors are normal. Handle them with try/catch/finally, throw Error objects (never strings), and use custom error classes for precision. Always clean up in finally. Re-throw when you can’t recover. Check the specific error type before the general one. And never swallow errors silently — that’s how bugs hide. Master error handling, and your programs fail gracefully instead of crashing.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!