ES2019: optional catch binding

[2017-08-13] dev, javascript, es2019, error handling
(Ad, please don’t block)

The proposal “Optional catch binding” by Michael Ficarra is at stage 4 and therefore part of ECMAScript 2019. This blog post explains how it works.

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.

Use cases  

There are two general reasons for omitting the catch binding:

  • If you want to completely ignore the error.
  • You don’t care about the error or you already know what it will be, but you do want to react to it.

My recommendation is to avoid doing that:

  • Instead of completely ignoring an error, at least log it to the console.
  • Instead of assuming you know what the error will be, check for unexpected types of exceptions and re-throw them.

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.

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;
    }    
}

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) {
    let id = (person && person.data && person.data.id) || 'No ID';
    console.log(id);
}

However, this shorter version is also more obscure.

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 and documenting code that ignores caught exceptions.

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;
}

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.

Further reading