ESC

Type to search the knowledge base.

Optional catch binding

Omit unused catch bindings with catch { } — when to ignore errors, when to log, and lint rules that keep this honest.

beginner3 min read
  • javascript
  • optional-catch

Older JS forced a binding even when you ignored the error:

try {
  JSON.parse(text);
} catch (e) {
  // e unused — lint noise
  return fallback;
}

Optional catch binding lets you write:

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

Same control flow. No binding. Supported in modern engines (ES2019).

When ignoring is legitimate

  • Parse best-effort UI config; fall back to defaults
  • Feature detection via try/catch (legacy)
  • Cleanup paths where the primary error is already handled elsewhere
function readJson(raw, fallback) {
  try {
    return JSON.parse(raw);
  } catch {
    return fallback;
  }
}

When you must bind

You need the object for:

  • Logging / reporting (Sentry.captureException(err))
  • Branching on err.name / err.code (AbortError, NotFoundError)
  • Rethrowing wrapped errors
try {
  await fetchProfile();
} catch (err) {
  if (err.name === 'AbortError') return;
  throw err;
}

Empty catch {} that swallows everything is a production footgun — failures vanish.

finally still works

let handle;
try {
  handle = await open();
  await work(handle);
} catch {
  // optional binding
} finally {
  await handle?.close();
}

finally runs whether you bind the error or not.

Linting

Many teams allow optional catch only with a comment or ban bare catch {} via ESLint (no-empty with allowEmptyCatch carefully). Policy > syntax sugar:

Policy Intent
Require catch (err) + log Observability first
Allow catch for pure parse fallbacks Local recovery
Never empty catch without comment Review signal

Transpile targets

If you ship untranspiled code to very old browsers, optional catch is a syntax error. Bundlers/Babel handle it when your browserslist demands it.

Interview answer (out loud)

“Optional catch binding means catch { } without a parameter when the error object isn’t used. It’s ES2019 syntax sugar. I still bind the error whenever I log, classify, or rethrow — silent empty catches hide bugs.”

Interaction with TypeScript

try {
  mightThrow();
} catch {
  // no binding — fine
}

try {
  mightThrow();
} catch (e) {
  // e is unknown in modern TS — narrow before use
  if (e instanceof Error) console.error(e.message);
}

Optional binding avoids unused-variable errors. When you bind in TS, treat unknown properly — don’t any your way out.

Nested try and partial recovery

try {
  try {
    parse(userJson);
  } catch {
    parse(defaultJson);
  }
  save();
} catch (err) {
  report(err);
}

Inner optional catch for “try alternate parse”; outer catch for real failures. Nesting is fine when each level has a clear policy.

Feature detection pattern

let structuredCloneImpl = globalThis.structuredClone;
if (typeof structuredCloneImpl !== 'function') {
  try {
    // legacy polyfill path may throw if unavailable
    structuredCloneImpl = polyfillStructuredClone;
  } catch {
    structuredCloneImpl = (v) => JSON.parse(JSON.stringify(v));
  }
}

Still prefer proper feature detection over try/catch when APIs exist as properties.

Further reading

Related guides