Lập trình · 21/10/2026

JavaScript catch: The Caught Value Is Not Always an Error

An error handler can fail while trying to read message. JavaScript allows many values to be thrown, so naming a catch binding error does not establish that it is an Error object. Treat caught values as unknown input at dependency boundaries.

JavaScript catch: giá trị bắt được không luôn là Error

An error handler can fail while trying to read message. JavaScript allows many values to be thrown, so naming a catch binding error does not establish that it is an Error object. Treat caught values as unknown input at dependency boundaries.

1. Throwing a string does not create an Error

try {
  throw 'dependency failed';
} catch (value) {
  console.log(typeof value); // string
  console.log(value.message === undefined); // true
  console.log(value); // dependency failed
}

The caught value is the original string. See MDN throw. This reproduces language behavior; prefer throwing Error instances in code you control for consistent diagnostics.

Reading message here loses the original text. A logger that records only that property may produce a failure event without useful detail. Test the data passed to logging rather than only whether catch executed.

2. Catch destructuring can fail before the handler body

try {
  try {
    throw null;
  } catch ({ message }) {
    console.log('inner handler reached');
  }
} catch (error) {
  console.log(error instanceof TypeError); // true
}

Object destructuring cannot operate on null, so the inner handler body never runs. The outer catch receives a new TypeError from binding initialization. See catch bindings in MDN try...catch.

A default for the message property does not solve a null input. Without a reliable contract, use a simple catch binding first and classify inside the body. Test null, undefined, numbers and custom objects explicitly.

3. Normalize at a boundary while retaining the original value

function normalizeCaught(value) {
  if (value instanceof Error) return value;
  return new Error('A non-Error value was thrown', { cause: value });
}
const original = new TypeError('invalid input');
console.log(normalizeCaught(original) === original); // true
const wrapped = normalizeCaught(null);
console.log(wrapped.message); // A non-Error value was thrown
console.log(wrapped.cause === null); // true

The helper retains same-realm Error identity, subtype and existing stack. Other values become causes of a new Error with a fixed message. cause can contain any value; see MDN Error.cause. These examples require runtime support for the cause option.

It neither reads an arbitrary object's message nor converts it to a string. Custom getters or conversion methods can execute code; retaining a cause is not a general serialization strategy.

instanceof Error is a simple same-realm policy. An Error from another iframe or VM may be wrapped again. Define and test that boundary separately if the application actually exchanges errors across realms.

4. Separate UI messages from diagnostics

Normalization establishes a consistent Error shape but does not choose a public message. Describe the failed operation to users and retain diagnostic causes according to the system's logging policy.

Include null, undefined, zero, false and empty strings in tests. A truthiness check cannot establish whether an error occurred because falsy values can be thrown too. Rethrowing the original value may be appropriate when the current layer adds no useful context or handling policy.

  • Use a simple binding for unknown caught values.
  • Read properties only under a suitable contract.
  • Preserve existing Errors when context need not change.
  • Retain original values when wrapping failures.
  • Test falsy causes and custom object behavior.

All six bilingual blocks ran with Node.js, alongside identity, falsy-cause and throwing-message-getter checks. Live logging and iframe integration were not tested.

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.