Specific Exception Handling

Hi,

Python has a syntax that lets us catch a specific kind of error. I'm surprised why this feature doesn't exist in Javascript.

This is the syntax I propose...

try {
  // ...
} catch (ex instanceof TypeError) {
  // ...
}

We can use multiple catch blocks to catch different errors...

try {
  // ...
} catch (ex instanceof TypeError) {
  // ...
} catch (ex instanceof EvalError) {
  // ...
}

We can use this when we need to do the same thing for different errors...

try {
  // ...
} catch (ex instanceof TypeError | EvalError) {
  // ...
}

The last block in the below tree will be executed if the exception is not a TypeError...

try {
  // ...
} catch (ex instanceof TypeError) {
  // ...
} catch (ex) {
  // ...
}

Thanks!

2 Likes

One problem is that some platforms/libraries use special properties on the error object instead of subclasses to make different error types (like node), so you would also need to provide a way to support them too. I had previously proposed providing a standard exception class to Javascript, which if used would make this kind of feature more palatable. The consensus in that proposal is that the design I came us with would probably break backwards compatibility, but it could still be possible to get everyone to use a standard system through a protocol, i.e. if an exception provided a unique key to a Symbol.exceptionType property (or just a non-symbol "code" property), and the catch clause would could check against a key. For example:

DOMException[Symbol.exceptionType] = 'DOM:DOMException'
try {
  doSomeDomOperation()
} catch 'DOM:DOMException' (ex) { 
  console.log('A DOM exception was thrown!')
}

If we don't get everyone onto a standard, then your catch handler would need to be flexible enough to handle exception subtypes and unique properties on the error object. Here's one possibility:

const isSubType = type => ex => ex instanceof type
const hasProperty = (key, value) => ex => ex[key] === value

try {
  ...
} catch where isSubType(DOMException) {
  ...
} catch where hasProperty('code', 'FileNotFound') {
  ...
}
1 Like

Not sure if I follow you. I meant this...

try {
  // ...
} catch (ex instanceof TypeError) {
  // ... 2
}

to be syntactic sugar over...

try {
  // ...
} catch (ex) {
  if    (ex instanceof TypeError) {
    // ... 2
  } else throw ex;
}

Thanks!

You don't need to limit yourself to instanceof. You could make catch accept any expression (where the exception would be bound to a fixed identifier, e.g. error or exc). The current form with a single name would be unaffected.

Or, you could replace catch with match:

try {
  ...
} match (ex) { // binds just like `catch (ex)`
  // but here you do pattern matching
}
1 Like

Yes, but take node for example. They don't subclass their fs errors, instead, they throw a regular error object and add a "code" property to them that explains what went wrong. For example, here's how you would handle a file-not-found error in node:

try {
  fs.readFileSync(...)
} catch (ex) {
  if (ex.code === 'ENOENT') {
    ...
  } else throw ex
}

Javascript has never officially declared that "subclassing exceptions is the way to go", so many people do it in different ways, and we need to be conscientious about the different solutions out there when we make a catch handler. Our options are to either to make this syntax support all options, or provide a non-backwards-compatible-breaking path for library implementers to use, that would unify the different types of exceptions (such as, asking people to provide a unique value for their exception in a symbol).

1 Like

@lightmare - the pattern-matching-catch thing is a good idea too. For reference, the pattern matching proposal actually shares almost exactly that as a potential solution to this problem, that could maybe be done in a follow-on proposal. See here.

And here's their code example:

try {
  throw new TypeError('a');
} catch match (e) {
  if (e instanceof RangeError) { … }
  when (/^abc$/) { … }
  else { throw e; } // default behavior
}
3 Likes

Hehe, I skimmed the readme looking exactly for that, and couldn't find it :D

2 Likes

instanceof doesn’t work for cross-realm builtins, so I’d be staunchly opposed to anything that encourages its semantics.

Catch guards are a proposal i will likely be championing after pattern matching advances closer to stage 4.

6 Likes

What if catch accepted a function that returned a truthy value?

try {
   let e = new Error;
   e.code = "SEGFAULT";
   throw e;
} catch (ex => e.code == "SEGFAULT") {
   try {
      throw new TypeError(e.code);
   } catch (ex2 => ex2 instanceof TypeError) {
      console.log("Caught them all!");
   }
}

what happens when that function throws an exception?

The problem I see with catch (someFunction) is that now you don't have a place to bind the thrown error to a variable, so such a function would need to be placed elsewhere, e.g. catch when someFunction (err)

If an error gets thrown inside the predicate, I'm not too concerned about what gets thrown from it, as long as something useful does. Such an error would only be caught or reported for debugging purposes, it's not something that should be recovered from. So, it could be that we bail the try-catch and let the error from the predicate be what get's propagated (similar to what happens when an error is thrown in a finally block). Or, we could throw an aggregate error containing both the original error and the error from the predicate. Either way works.

In my types notes there's a more compact syntax that I saw someone else suggest years ago:

catch (e:TypeError|EvalError => e.message == 'a') { console.log(e); }

That said ljharb's comment is why I haven't moved it outside the future consideration notes. Being able to throw in the "when" part complicates things. Can return false or throw outside of the try. I don't even know what C# does in these cases.

It should be the same thing that happens when a normal catch throws. Remember that this is just candy for having exception type checking in the catch body.

For this, there would be a few syntax restrictions, namely that the function must be an arrow function with no parenthesis for the single required parameter and a single non-block expression for the function body. The parameter variable would be passed on to the catch body.