Try/catch conditional exceptions in light of generators

# Benjamin (Inglor) Gruenbaum (7 years ago)

Traditionally, JavaScript code contains some sort of asynchronous logic either on the client side or on the server side.

This makes the try/catch construct non-practical for many real use cases because it can not catch errors caused by callbacks and/or other asynchronous operations involved. This is why we have common idioms like the (err,result) of NodeJS callbacks or reject in promises implementations and so on.

NodeJS also introduces domains which let you catch all errors that originated from a piece of code in a certain way.

ES6 Generators recently implemented in NodeJS allow us to do concurrency using coroutines. This allows for very nice code that's very readable in certain cases. The async_functions proposal is also very interesting solving the problem without needing a "runner" for the generator.

However, one issue arises from using generators for such concurrency. The current way try/catch works with generators does not give us a way to differentiate things like syntax errors from logic errors.

Consider the following piece of code using Bluebird promises:

Promise.coroutine(function *(){
    try{
        let db = yield DBEngine.open("northwind");
        let result = yield db.query("SELECT name FROM users")
        return res;
    } catch (e){
        //code to handle exception in DB
    }
});

Noticed the ReferenceError? Not obvious at first glance. A compile time tool might find it but I may have a closure variable named res. This might even happen later when making changes. A catch-all does not do a good job here. What I'd want to do is to catch all exceptions that are mine. I need a way to catch exceptions of a different type.

This is a real need in light of the new found usage for try/catch given generators.

I've looked around and found one possible solution using catch guards ( strawman:catch_guards ) , I know Firefox supports catch guards but I was unable to find anything in the specification or in the mailing list when searching relating to it.

Petka added:

Without predicated catch clauses you would need to write something like this:

Promise.coroutine(function *(){
    try{
        let db = yield DBEngine.open("northwind");
        let result = yield db.query("SELECT name FROM users")
        return res;
    } catch (e) {
        if( e instanceof RejectionError ) {
            //code to handle exception in DB
        }
        else {
            throw e;
        }

    }
});

The manual rethrow can easily be forgotten to be written

# Domenic Denicola (7 years ago)

I am curious what, if anything, about this problem has to do with asynchronicity? That seems to be a key part of your point, but I don't understand why you wouldn't phrase the problem entirely in terms of synchronous operations since nothing seems unique to async.

# Benjamin (Inglor) Gruenbaum (7 years ago)

That's a good question.

I try to discuss problems I run into. Developers, me included have found ways to minimize reliance on try/catch in synchronous operations using monadic maybies and checking values.

It's very often in JavaScript that I want to handle exceptions that are related to I/O and/or external resources. In JavaScript these operations are almost always asynchronous.

It's very common to handle the issue of asynchronously handling exceptions using the mechanism that is used to wrap the asynchronous operation.

  • If it's a callback people typically use the (err, first parameter convention.
  • If it's a promise people use a "rejected" callback, a common (but not the prettiest) example is .fail in jQuery.

Both these options don't suppress things like syntax errors, reference errors etc. There are of course other ways to do asynchronous operations and I/O but more generally, mechanisms that go around the language error handling constructs do a pretty good job at this.

Generators give me a new great way to do concurrency, they reduce code and increase readability. However - they bring me back to the try/catch of the language which does not make the distinction or allow me address the issue without wrapping the catch with more code.

# Benjamin (Inglor) Gruenbaum (7 years ago)

Any form of reply on this question/problem would be highly appreciated. I believe this is a real use case and I'd like to know what other people think.

I've had to deal with this multiple times since I sent this to es-discuss.

# Brendan Eich (7 years ago)

Benjamin (Inglor) Gruenbaum wrote:

Any form of reply on this question/problem would be highly appreciated. I believe this is a real use case and I'd like to know what other people think.

I've had to deal with this multiple times since I sent this to es-discuss.

Can you show exact code excerpt with a comment-pointer to the buggy bit, and then talk about the fix? It's really not clear what your "this" refers to.

# Benjamin (Inglor) Gruenbaum (7 years ago)

thanks for the comment and sorry for the unclarity.

I was refering to my original post in this thread about try/catch with generators: esdiscuss.org/topic/try-catch-conditional-exceptions-in-light-of-generators#content-0

Now that I started working more with generators - I find handling exceptions really difficult now that I use try/catch in a synchronous way. Just to be clear - I'm not suggesting a fix, I'm raising an issue me and some of my colleagues I've been facing and I'm interested to know if:

  • There is a consensus that this is a problem.
  • It's a big enough problem for other people too or it sounds like a big enough problem in case they have not tried coding asynchronous code with generators yet.
  • Someone already solved this, in which case I'd love to learn how (and share that knowledge on the MDN wiki).

In case the code in the original post is unclear please let me know and I'll try to add a clearer sample.

# Brendan Eich (7 years ago)

Benjamin (Inglor) Gruenbaum wrote:

I was refering to my original post in this thread about try/catch with generators: esdiscuss.org/topic/try-catch-conditional-exceptions-in-light-of-generators#content-0

Yes, but the Domenic pointed out that the reference error there has nothing to do with generators so much as async control flow in general (it could be in a function passed as a callback or downward funarg, and called "later" by any means). And you seemed to agreed.

It's true that yield allows part of a function to run in a later event loop turn -- or just in a later iteration of a loop in the same turn. Generators by themselves are not async in the event loop sense. So first you need to identify the problem precisely.

If you write

function f() { ... setCallback(function g() {... RefErrHere ... }) ... }

you need test coverage over the body of g.

If you write

function* f() { ... yield; RefErrHere ... }

same deal. There is no difference in kind.

So, is the problem you are citing a general one of test coverage over async control flow?

# Jeremy Martin (7 years ago)

I can't speak for Benjamin, but I would like to "+1" the notion that try/catches could benefit from some form of error pattern matching. Here's an example that attempts to avoid conflation with async control-flow considerations:

try {
    JSON.parse(getJSONString());
} catch (err) {
    if (err instanceof SyntaxError) {
        // handle the error
    } else {
        throw err;
    }
}

Discriminatory error handling like this is not all that uncommon, and could potentially benefit from an expansion of the existing strawman:guards. For example:

try {
    JSON.parse(getJSONString());
} catch (err :: SyntaxError) {
    // handle the error
}

This of course has larger implications, though, as you'd presumably need to support multiple "guarded" catches, and behavior for a failed guard is different in the try/catch scenario than with parameters or properties.

# Adam Crabtree (7 years ago)

Better error handling in JavaScript is incredibly important. As I see it, there are 3 fundamental roadblock or obstacles that undermine the utility of JavaScript's error handling:

  1. Lack of typed catch
  2. Lack of Error.create (solved from what I understand to some degree by Object.setPrototypeOf)
  3. Lack of optimization of try/catch code (in V8)1

I'll only talk about #1, but, briefly, #3 is additionally important because of its real world ramifications (e.g., node.js' explicit non-use of try/catch to avoid it2). I conjecture fixing #3 has not been made a higher priority because of the weakness of JavaScript error handling due to .#1 and #2.

Regarding typed catch, typed catch is not just sugar. The above instanceof check works, but rethrowing obfuscates the reported throw line. node.js again is an example of a codebase that adapts to this by never attempting a typed catch to avoid rethrowing.

Async code, control-flow, and async generators all largely owe their origins in JavaScript to try/catch.

As an aside, node.js error handling is fundamentally broken and inconsistent and promises violate assumptions by catching errors originating in core.3

Thunks (callback(err, result)) and consequently promises arose out of async programming in JavaScript and the inability to catch errors from a different event loop tick.

Because try/catch could not catch errors in another tick, errors could not be caught and thus should not be thrown, and thus the thunk was born. Promises are a monadic alternative to callbacks, but not to try/catch unless you wrap all function calls (even synchronous non-IO) in a promise, which will slow your execution by separating synchronous execution across event loop ticks per the spec, effectively a non-starter. CPS is a true alternative to try/catch/throw, except for the lack of proper tail calls.

So, we have 3 options:

  1. Monadic approach (e.g., Maybe)
  2. CPS (requiring a trampoline in lieu of PTC)
  3. try/catch/throw (requiring workarounds to avoid DEOPT)

There are tradeoffs to each approach. #3 is the language default since the first two have significant architectural implications, or at least require opt-in. There is technically a 4th common alternative of never catching. In node though, this leads to requiring a process restart and DoS liability since core shares the same stack as userland (application) code and throwing places it into an undefined state by not allowing the stack to unwind (they should be catching). I wrote the async trycatch module4 to solve this.

Summary:

With async generators and the ability to use the default language error handling construct (try/catch) across async IO, try/catch is the best error handling mechanism in JavaScript, no longer requiring ugly hacks like my trycatch module. However, without typed catch, error handling is ugly and inconvenient, resulting in anti-patterns like the following

try{
  // some code
  JSON.parse(foo)
  // some code
} catch(e) { /* not always what you think it is */ }

that typed catch would easily solve.

# Benjamin (Inglor) Gruenbaum (7 years ago)

Excellent summary. I'm not convinced types are the way to go about this but a stronger try/catch is something I'd really enjoy.

In two lines most of the code I read today bypasses the built in exception handling mechanism in favor of CPS or (err,callback) because of how weak catch is and asynchronousity. Now generators let us solve the asynchronousity issue letting but we're still stuck with try/catch.

Brendan, the reason I find generators related is that most of the time I want to distinguish I/O exceptions (which I want to handle closely) from things like SyntaxError and ReferenceError (which I usually don't want to handle at the same granularity). I think the language should help me do that.

# Domenic Denicola (7 years ago)

One idea I had that could be a component of the solution would be that all spec-thrown exceptions get an extra flag. So e.g.

try {
  iDoNotExist();
} catch (e) {
  assert(e.thrownBySpecMechanisms);
}

You would generally use this via

try {
  someOperation();
} catch (e) {
  if (e.thrownBySpecMechanisms) {
    // I am only prepared to handle operational failures, not typos or
    // trying to write to non-writable properties or the like.
    throw e;
  }

  handleOperationalError(e);
}

You could then imagine some syntactic sugar of the sort

try {
  someOperation();
} recover (e) {
  // `recover` only handles non-spec-thrown exceptions.
  handleOperationalError(e);
}

This would be an alternative to the requirement that all authors suddenly agree on a new flag (or worse, subclass) to distinguish their errors from spec-thrown errors, and then do

try {
  someOperation();
} catch (e) {
  if (!e.crossLibraryStandardFlagForLibraryThrownNotSpecThrown) {
    throw e;
  }
  handleOperationalError(e);
}

which as per previous complaints on this thread also has the problem that you lose stack trace information.

# Brendan Eich (7 years ago)

You don't need to sell me -- we had } catch (e if ...) { in SpiderMonkey and proposed it for ES3 (in 1998, IIRC -- shaver did the work).

On the other hand, types (meaning static types) are not happening, as we discus and re-discuss here often. Guards are not near consensus.

Refutable patterns are on the boards for ES7, also not near consensus:

strawman:pattern_matching

I'm still not clear how any of this helps Benjamin, but I don't quite understand what Benjamin is running into.

# Brendan Eich (7 years ago)

Benjamin (Inglor) Gruenbaum wrote:

Brendan, the reason I find generators related is that most of the time I want to distinguish I/O exceptions (which I want to handle closely) from things like SyntaxError and ReferenceError (which I usually don't want to handle at the same granularity). I think the language should help me do that.

Thanks for clarifying.

Refutable pattern matching for ES7 must include catch patterns. We're still working on it. Suggest starting a fresh thread on the proposal:

strawman:pattern_matching

# Sam Tobin-Hochstadt (7 years ago)

On Wed, Nov 20, 2013 at 12:35 PM, Brendan Eich <brendan at mozilla.com> wrote:

I don't quite understand what Benjamin is running into.

I think that Benjamin's point is that generators allow you to use synchronous programming models instead of async ones. Then your errors become exceptions instead of errback calls. So, whereas ES5-style JS features very few exceptions, ES6-style will feature more exceptions. This increases the pressure on exception handling to be nice, which it isn't because of the lack of catch guards.

# Brendan Eich (7 years ago)

Sam Tobin-Hochstadt wrote:

On Wed, Nov 20, 2013 at 12:35 PM, Brendan Eich<brendan at mozilla.com> wrote:

I don't quite understand what Benjamin is running into.

I think that Benjamin's point is that generators allow you to use synchronous programming models instead of async ones. Then your errors become exceptions instead of errback calls. So, whereas ES5-style JS features very few exceptions, ES6-style will feature more exceptions. This increases the pressure on exception handling to be nice, which it isn't because of the lack of catch guards.

Yeah, if you read up, you'll see that this became clear just recently in-thread.

Yay for refutable patterns, including in catch heads.

# Benjamin (Inglor) Gruenbaum (7 years ago)

Honestly I'm really not sure how I feel about this.

Guards are a huge deal. They're a big (and nice!) feature, it sounds like they bring a lot of complexity (specifying them, not using then). They'll likely require a lot of work and debate in order to make it in (or not).

I have a very real and very simple use case I run into constantly. Like you said - if I used Mozilla's JS engine I'd be able to do a conditional catch but I don't have the luxury of only developing for Mozilla (I'll use it in Firefox OS for sure though :) ). For example, I write a lot of node which doesn't have conditional catch.

(I completely agree about type conditions not being the right solution - behavior seems like a much better option)

You tell me I don't need to sell you this - who do I need to sell this to? What people do I convince in order to have better catch clauses that solve my problem in the spec?

Thanks, Benjamin

# Brendan Eich (7 years ago)

Benjamin (Inglor) Gruenbaum wrote:

You tell me I don't need to sell you this - who do I need to sell this to? What people do I convince in order to have better catch clauses that solve my problem in the spec?

I think we need a champion for the pattern matching strawman. Dave was championing but may need a tag-team partner. Cc'ing him.

# Andreas Rossberg (7 years ago)

On 21 November 2013 01:55, Brendan Eich <brendan at mozilla.com> wrote:

Benjamin (Inglor) Gruenbaum wrote:

You tell me I don't need to sell you this - who do I need to sell this to? What people do I convince in order to have better catch clauses that solve my problem in the spec?

I think we need a champion for the pattern matching strawman. Dave was championing but may need a tag-team partner. Cc'ing him.

I can do that. I wanted to take it up anyway.

# Benjamin (Inglor) Gruenbaum (7 years ago)

Thanks Brendan,

Refutable patterns not being near consensus sounds like getting this done could be a lot of work both implementing it and figuring out a proposal people agree on.

Maybe I'm understanding it wrong, but the related bit is:

GuardedPattern(refutable) ::= Pattern(refutable) "if" AssignmentExpression

Where all I really need to solve this problem is:

"catch" "(" IdentifierName "if" AssignmentExpression ")"

Adding a Pattern to a catch, while it sounds fancy (I'm all up for patterns!) is something that can be done either in addition to, or on top of something like "if" AssignmentExpression bit - it's not a part of the current proposal either.

I really appreciate your time, effort and knowledge on this. You've been nothing but nice, and a great help for me when discussing issues here. Given how everyone I talked to here and in forums like the idea of stronger catch clauses and assuming you're already up for better catch clauses - who do I need to convince that this is a real use case?

Can I take a look at the catch (e if) { proposal? Is it available online? What were the objections to it?

Thanks, Benjamin

# Brendan Eich (7 years ago)

Benjamin (Inglor) Gruenbaum wrote:

Can I take a look at the catch (e if) { proposal? Is it available online? What were the objections to it?

That was 15 years ago, so lost in the ashes of Netscape, I think. Well before es-discuss or the modern era.

Let's see what Andreas comes up with. We are on a rapid release cycle with ES7 now, and we implement ahead of finalizing specs. Instead of doing a micro-spec for catch guards that might commit us to something we don't want in the fuller pattern matching proposal.

So, we need the well-championed, thought-through, pattern-matching proposal first. It won't take too long, I bet, and in any event you are not going to get browsers implementing catch-if quickly -- I am 100% sure of this, based on long experience.

# Jason Orendorff (7 years ago)

On Thu, Nov 21, 2013 at 12:55 PM, Brendan Eich <brendan at mozilla.com> wrote:

So, we need the well-championed, thought-through, pattern-matching proposal first. It won't take too long, I bet, and in any event you are not going to get browsers implementing catch-if quickly -- I am 100% sure of this, based on long experience.

And amen to that.

Benjamin, adding one language feature for every reasonable use case presented on es-discuss sounds like a great way to get a language full of incoherent hacks in a hurry. That is, I don't think you'd like the result you're asking for, if you just directly extrapolate that to everyone else's JS pain point that can be addressed with one tiny leetle waffer-thin bit of syntax.

A platform can have a hundred thousand APIs. A programming language can't have a hundred thousand features. The complexity budget is finite. We've got to add language features that work in multiple syntactic contexts and support multiple use cases.

# Benjamin (Inglor) Gruenbaum (7 years ago)

Here's how I see this post:

  • Performing Evented I/O well is one of the biggest use cases of JavaScript - in the browser, in mobile apps, on the server and generally.
  • Exceptions in I/O scenarios did not traditionally post a problem since constructs like callbacks and promises were used.
  • Generators were introduced in ES6, allowing developers like me to write code without needing many of those constructs. This fundamentally changes the way I can write JavaScript.
  • With generators, I can write try/catch clauses using the native language syntax. That's nice.
  • Try/catch clauses don't have any form of guards in ES, meaning I can't really catch logic errors and syntax reference/errors differently in a nice way. When adding I/O errors to the mix the problem gets magnified times 100 as my first example shows.

I'm not even saying I think catch(e if is the solution. I'm not saying I have a solution I'm content with. I'm not even saying I have a solution at all. If it sounds like I was saying "everyone should just implement catch.. if right way" then I apologize for that and it wasn't my intent.

This is why I posted:

  • Hey, I'm having the issue, are you having it too? Is it a real problem?
  • If so, are there clever ways to deal with it?
  • If so, what are they, are there proposals on the way?
  • If so, since this is an important issue to me, how can I promote it being addressed?

I think that the first of these has been established, there was a suggestion raised about the third and I have no idea about the fourth. I do however, admit that the patterns proposal doesn't sound to me like it has enough to solve this use case, or that I understand how the two are that connected (which is what I tried saying in that post).

# Kevin Smith (7 years ago)

I think that the first of these has been established, there was a suggestion raised about the third and I have no idea about the fourth. I do however, admit that the patterns proposal doesn't sound to me like it has enough to solve this use case, or that I understand how the two are that connected (which is what I tried saying in that post).

Patterns would presumably address your use case by allowing you to specify a structural type test against the error object.

# Benjamin Gruenbaum (5 years ago)

Hey, I just wanted to point out that this is now discussed in groundwater/nodejs-symposiums#5

It appears that there are no proposals on the way to deal with it and it is a very real problem. What would be the correct process to bring more attention to it?

# Kevin Smith (5 years ago)

It appears that there are no proposals on the way to deal with it and it is a very real problem. What would be the correct process to bring more attention to it?

A proposal posted here : )

A few things to address (off the top of my head):

on refutable pattern matching strawman:pattern_matching. Should

the catch syntax be based on pattern matching? If not, why not? If so, then we'll need an updated proposal for pattern matching first.

  • If, instead of pattern matching, catch guards are based around some kind of runtime "typing" (using that word loosely), how does that work with Realms and duplicated libraries?
# Vlad Fedosov (4 years ago)

Hi! Is there is any way to push this proposal forward?