Exploring ES2018 and ES2019
Please support this book: buy it or donate
(Ad, please don’t block.)

17. Optional catch binding



This chapter explains the ES2019 feature “Optional catch binding” by Michael Ficarra.

17.1. Overview

The proposal allows you to do the following:

try {
  // ···
} catch {
  // ···
}

That is useful whenever you don’t need the binding (“parameter”) of the catch clause:

try {
  // ···
} catch (error) {
  // ···
}

If you never use the variable error, you may as well omit it, but JavaScript doesn’t let you do catch (). Furthermore, linters that check for unused variables complain in such cases.

17.2. Use cases

There are two general reasons for omitting the catch binding:

My recommendation is to avoid doing that:

If you can’t and don’t want to avoid it, I suggest encapsulating your code, e.g. inside a function, and to document it well.

Next, we’ll take a look at use cases for omitting catch bindings and at risks and alternatives.

17.2.1. Use case: JSON.parse()

With JSON.parse(), there is one predictable kind of exception – if the input is not legal JSON:

> JSON.parse('abc')
SyntaxError: Unexpected token a in JSON at position 0

That’s why it can make sense to use it like this:

let jsonData;
try {
  jsonData = JSON.parse(str); // (A)
} catch {
  jsonData = DEFAULT_DATA;
}

There is one problem with this approach: errors in line A that are not related to parsing will be silently ignored. For example, you may make a typo such as JSON.prase(str). Cases like this have bitten me a few times in the past. Therefore, I now prefer to conditionally re-throw the errors I catch:

let jsonData;
try {
  jsonData = JSON.parse(str);
} catch (err) {
  if (err instanceof SyntaxError) {
    jsonData = DEFAULT_DATA;
  } else {
    throw err;
  }  
}

17.2.2. Use case: property chains

When accessing nested properties that may or may not exist, you can avoid checking for their existence if you simply access them and use a default if there is an exception:

function logId(person) {
  let id = 'No ID';
  try {
    id = person.data.id;
  } catch {}
  console.log(id);
}

I prefer explicit checks. For example:

function logId(person) {
  let id = 'No ID';
  if (person && person.data && person.data.id) {
    id = person.data.id;
  }
  console.log(id);
}

This code can be shortened if you consider that the && operator returns the first falsy operand or the last operand (if there is no falsy operand):

function logId(person) {
  const id = (person && person.data && person.data.id) || 'No ID';
  console.log(id);
}

However, this shorter version is also more obscure.

17.2.3. Use case: assert.throws()

Node.js has the API function assert.throws(func) that checks whether an error is thrown inside func. It could be implemented as follows.

function throws(func) {
  try {
    func();
  } catch {
    return; // everything OK
  }
  throw new Error('Function didn’t throw an exception!');
}

This function is an example of wrapping an documenting code that ignores caught exceptions.

17.2.4. Use case: feature detection

The following code snippet demonstrates how to detect whether a given feature exists:

let supported;
try {
  useTheFeature();
  supported = true;
} catch {
  supported = false;
}

17.2.5. Use case: even logging fails

If even logging doesn’t work then, as a last resort, you have no choice but to ignore exceptions (because further logging could make things worse).

function logError(err) {
  try {
    // Log or otherwise report the error
    console.error(err);
  } catch {} // there is nothing we can do
}

Again, we encapsulate and document the slightly unorthodox code.

17.3. Further reading