Optional catch binding
Omit unused catch bindings with catch { } — when to ignore errors, when to log, and lint rules that keep this honest.
- 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
- try catch finally Best Practices
- Error Types and Custom Errors
- Promises
- JSON parse and stringify Pitfalls
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.