Tutorials Logic, IN info@tutorialslogic.com

JavaScript Error Handling try catch finally: Causes and Fixes

JavaScript Error Model

Synchronous code reports failures by throwing, while promises report them through rejection. try/catch handles only throws that occur during its protected synchronous execution or awaited promises inside an async function. A promise started without await is not caught by the surrounding synchronous catch.

Error Handling

While executing the JavaScript code, different errors may occur. These errors can be a syntax error, logical error, and runtime error. There are several ways of handling them:-

The try and catch:- The try statement is a block of code that lets us test a block of code for errors, while the catch statement is a block of code that lets us handle the error.

The throw statement produces an abrupt completion that transfers control to an applicable error boundary.

The finally block performs cleanup before control leaves the try and catch construct.

Javascript Error Handling Syntax

Javascript Error Handling Syntax
try {
	expression;
}
catch(error) {
	expression;
}

Javascript Error Handling Worked Example

Javascript Error Handling Worked Example
var a = 10;
var b = 20;
try {
	console.log(a + b);
}
catch(error) {
	console.log(error);
}

Javascript Error Handling Worked Example 2

Javascript Error Handling Worked Example 2
var x;
try {
	if(x == "") throw "Empty";
	if(isNaN(x)) throw "Not a number";
}
catch(error) {
	console.log(error);
}

Javascript Error Handling Worked Example 3

Javascript Error Handling Worked Example 3
var x;
try {
	if(x == "") throw "Empty";
	if(isNaN(x)) throw "Not a number";
}
catch(error) {
	console.log(error);
} finally {
	console.log("Finally block will always execute!")
}

JavaScript Error Types

JavaScript has several built-in error types. Understanding them helps you write more targeted error handling.

Error Types

Error Types
// ReferenceError - accessing undefined variable
try {
  console.log(undeclaredVar);
} catch (e) {
  console.log(e instanceof ReferenceError); // true
  console.log(e.message); // undeclaredVar is not defined
}

// TypeError - wrong type operation
try {
  null.property;
} catch (e) {
  console.log(e instanceof TypeError); // true
}

// RangeError - value out of range
try {
  new Array(-1);
} catch (e) {
  console.log(e instanceof RangeError); // true
}

// SyntaxError - caught at parse time, not runtime
// eval('if (');  // SyntaxError

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

try {
  throw new ValidationError('Email is required', 'email');
} catch (e) {
  console.log(e.name);    // ValidationError
  console.log(e.field);   // email
  console.log(e.message); // Email is required
}

Async Error Handling

When working with Promises and async/await, error handling requires special attention.

Async Error Handling - JavaScript Example

Async Error Handling - JavaScript Example
// Promise .catch()
fetch('/api/data')
  .then(res => res.json())
  .catch(err => console.error('Fetch failed:', err));

// async/await with try-catch
async function loadUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
    const user = await res.json();
    return user;
  } catch (err) {
    console.error('Failed to load user:', err.message);
    return null;
  } finally {
    console.log('Request complete');
  }
}

// Global unhandled rejection handler
window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled promise rejection:', event.reason);
  event.preventDefault();
});

Preserving Errors

Throw Error objects or meaningful subclasses so stack and cause information remain available. Catch only where code can add context, recover, or translate the failure for a boundary. Rethrow unexpected failures instead of converting every defect into a vague success value.

Use finally for cleanup that must run after success or failure, but avoid returning from finally because it can replace the original result or exception. At UI boundaries, show safe messages and log diagnostic details without tokens or personal data.

Error Types and Boundaries

JavaScript provides `Error` and built-in subclasses such as `TypeError`, `RangeError`, `ReferenceError`, `SyntaxError`, `URIError`, and `AggregateError`. Use an error type to describe a failure category, not to turn every status into an exception. Expected outcomes such as an empty search result can be ordinary values, while violated invariants and failed operations should take an explicit failure path.

Throw `Error` objects rather than strings so callers receive a name, message, stack, and optional cause. A catch binding can still contain any thrown value, including a string or object from third-party code, so normalize unknown values at an integration boundary before reading error properties.

Place a catch block where the code can recover, translate, compensate, or report once. Catching at every function and rethrowing creates duplicate logs without new ownership. Lower layers should include technical context; application boundaries translate failures into user-safe results, protocol responses, queue outcomes, or incident telemetry.

Custom errors are useful when callers need stable categories or structured safe details. Set a clear name, call `super`, and keep private payloads out of messages likely to reach logs or users. Prefer a stable code for machine decisions because message text can change for clarity or localization.

  • Throw Error instances with actionable context.
  • Treat caught values as unknown until normalized.
  • Handle each failure at one ownership boundary.
  • Keep stable machine codes separate from display messages.

Cause, Rethrow, and Aggregation

When adding context, create a new error with the original value in the `cause` option. This preserves a navigable chain without copying the original message into every layer. Do not assume `cause` is an Error; the language permits any value. Telemetry serializers should bound cause depth and handle cycles or unsupported fields safely.

Rethrow unchanged when the current layer cannot add a useful contract. Selective catches should handle only the expected category and throw all others. A catch that returns a fallback for every error can hide programmer defects, permission failures, corrupt data, and cancellation as if they were one ordinary absence.

`AggregateError` represents several related failures, such as when `Promise.any` receives only rejections or a batch needs to report multiple independent item errors. Preserve item identity alongside each failure. Do not make a user inspect a giant aggregate when the workflow can show field-level or item-level outcomes directly.

Errors crossing workers, realms, network APIs, or process boundaries may lose prototypes and non-enumerable properties. Serialize an explicit safe shape with code, message intended for that boundary, operation ID, and selected details. Reconstructing an instance must not make untrusted remote data equivalent to a locally trusted stack trace.

  • Attach the original failure through cause when adding context.
  • Rethrow categories the current layer cannot handle.
  • Associate aggregate failures with their input items.
  • Serialize a bounded safe error contract across boundaries.

Async Failures and Cleanup

A synchronous throw transfers control to the nearest active matching catch. A rejected Promise is observed through `await`, `.catch`, or a rejection handler. A `try` block around a call does not catch a rejection unless that Promise is awaited inside the block. Give every launched Promise an owner so rejection is never accidentally detached.

`finally` executes as control leaves the try/catch construct, including after return or throw. Use it to release locks, clear timers, hide loading state, close resources, or restore temporary configuration. Avoid returning or throwing a new unrelated error from finally because it can replace the original completion and obscure the cause.

When several resources are acquired, clean up only those successfully acquired and generally release in reverse order. Cleanup can fail too; preserve the primary error and report cleanup failure without silently replacing it. Make cleanup idempotent so cancellation, timeout, and explicit teardown can converge safely.

Cancellation is not always an operational error. An aborted stale search may need no user alert, while a user-cancelled upload may need a resumable state. Classify cancellation at the owning workflow and still run cleanup. Do not retry an abort unless a new owner explicitly starts new work.

  • Await a Promise inside try when its rejection belongs there.
  • Release partially acquired resources safely.
  • Preserve the primary failure if cleanup also fails.
  • Distinguish cancellation from retryable dependency failure.

Reporting, Retry, and Recovery

Browser `error` events can report uncaught synchronous script errors and resource failures according to event behavior, while `unhandledrejection` reports Promises without a rejection handler. Use these as last-resort telemetry. They cannot know the correct business recovery, and cross-origin or privacy behavior can limit available detail.

Record operation, release, route, safe user or tenant correlation, error code, cause class, and stack where available. Redact credentials, personal data, request bodies, and tokens. Group by stable fingerprint rather than message text containing identifiers, and preserve source maps securely so minified production stacks can be resolved.

Retry only transient failures and only idempotent or safely reconciled operations. Bound attempts, back off with jitter, respect a total deadline, and stop when the user cancels. Validation, authorization, programming defects, and most not-found outcomes do not become successful through repeated requests.

Test failure at every side-effect boundary: before a write, after a remote commit with a lost response, during parsing, while cleaning up, and after a partial batch. Verify user state, durable data, retry behavior, logs, and alerts. A unit test that merely expects an exception does not prove the workflow recovers safely.

  • Use global handlers for telemetry, not local recovery.
  • Redact sensitive values before error reporting.
  • Retry only classified transient and repeatable operations.
  • Test failures before and after durable side effects.

Operational Error Taxonomy

Define a small taxonomy from decisions the application must make. Validation errors map to field correction, authentication errors to sign-in or token refresh, authorization errors to a denied action, conflicts to reload or merge, rate limits to controlled delay, transient dependencies to bounded retry, and programming defects to safe failure plus engineering investigation. Do not use HTTP status alone as the internal model.

Mark retryability, user visibility, severity, and reportability explicitly at the boundary that understands the operation. The same network exception can be retryable for an idempotent catalog read and unsafe for a payment whose server outcome is unknown. Reconcile ambiguous writes with an operation key before either reporting failure or trying again.

Map internal failures to external responses without leaking stack traces, database text, file paths, or policy details. Give users a stable reference or operation ID when support can use it. Preserve the detailed cause in protected telemetry, and return enough safe context for the caller to choose retry, correction, reauthentication, or escalation.

Review the taxonomy after incidents. If many errors become “unknown,” add classification at the integration boundary rather than string-matching messages throughout the application. If one code covers unrelated failures, split it only when callers need different behavior. Error design is an API contract and should evolve deliberately.

  • Classify failures from the recovery decision they require.
  • Determine retryability per operation, not per error name alone.
  • Keep external messages safe and internal causes traceable.
  • Version stable error codes as part of the API contract.
Before you move on

JavaScript Error Handling try catch finally: Causes and Fixes Mastery Check

5 checks
  • While executing the JavaScript code, different errors may occur.
  • These errors can be a syntax error, logical error, and runtime error.
  • There are several ways of handling them:-.
  • Throw Error objects so callers receive a message, stack, type, and optional cause.
  • Avoid return statements in finally because they can replace an earlier result or error.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.