Are thrown errors in a try block considered to be handled even if there's no catch block?

Andy Earnshaw andyearnshaw at gmail.com
Fri Jun 23 14:53:36 UTC 2017


Thanks, Domenic, I had a quick look at that and I hope that other
implementers show some interest.  It's certainly a step in the right
direction, although it also suffers from not being able to break on
(caught) exceptions in the debugger.

I know that there are also efforts to allow EventTarget construction; I saw
your PR for this the other day.  That's probably what I would use in the
future.

On Fri, 23 Jun 2017 at 14:16 Domenic Denicola <d at domenic.me> wrote:

> Indeed, you cannot replicate dispatchEvent’s behavior, because it catches
> the error, then uses a browser-specific primitive “report an exception”.
> Over in the HTML spec, we’ve suggested exposing that primitive to users,
> but it hasn’t garnered sufficient implementer interest; see
> https://github.com/whatwg/html/pull/1196.
>
>
>
> *From:* es-discuss [mailto:es-discuss-bounces at mozilla.org] *On Behalf Of *T.J.
> Crowder
> *Sent:* Friday, June 23, 2017 07:01
> *To:* Andy Earnshaw <andyearnshaw at gmail.com>
> *Cc:* es-discuss <es-discuss at mozilla.org>
> *Subject:* Re: Are thrown errors in a try block considered to be handled
> even if there's no catch block?
>
>
>
> > Are thrown errors in a try block considered to be handled
>
> > even if there's no catch block?
>
>
>
> An exception propagates out of a function (and thus is ultimately reported
> unhandled if unhandled) if it's what terminates the function. If code in
> the `finally` block does something to prevent the original exception
> terminating the function (by continuing a loop within the function,
> returning something, throwing a different exception, etc.), then the
> (original) exception doesn't propagate.
>
>
>
> > If you swap out the catch block for a finally block (with either
>
> > `continue` or some kind of recursive iteration), the errors
>
> > aren't technically handled, but only that last one is
>
> > considered "uncaught".
>
>
>
> The last one will only be special if you treat it differently from the
> previous ones. I *think* you mean something like this:
>
>
>
> ```js
>
> for (let i = 0; i < 3; ++i) {
>
>     try {
>
>         throw i; // E.g., code that may throw
>
>     } finally {
>
>         if (i < 2) { // If we're not on the last iteration
>
>             continue;
>
>         }
>
>     }
>
> }
>
> ```
>
>
>
> There, by using `continue` in the `finally` block (for all but the last
> one), we're preventing the exception from propagating because we've changed
> the completion of the block from 'throw' to 'continue', details:
>
>
>
> * [The `continue` statement - Runtime semantics - Evaluation][1]
>
> * [The `try` statement - Runtime semantics - Evaluation][2]
>
> * and the various loop definitions, for instance [The `for` statement -
> Runtime semantics - ForBodyEvaluation][3].
>
>
>
> I think that's the answer to your question about `finally`.
>
>
>
> The core issue you're having, replicating `dispatchEvent`'s behavior, is
> fascinating; I don't think you can do what it does (at least, what it does
> on Chrome), because it calls the handlers *synchronously*, allowing their
> exceptions to propagate (synchronously), but also continuing its
> synchronous loop through the handlers. I found the results of this code
> fascinating, for instance (https://jsfiddle.net/krdqo1kw/):
>
>
>
> ```js
>
> Promise.resolve().then(_ => console.log("then"));
>
> const target = document.createElement('div');
>
> target.addEventListener('foo', e => {
>
>     console.log("1");
>
>     throw 1;
>
> });
>
> target.addEventListener('foo', e => {
>
>     console.log("2; cancelling");
>
>     e.stopImmediatePropagation();
>
>     throw 2;
>
> });
>
> target.addEventListener('foo', e => {
>
>     console.log("3");
>
>     throw 3;
>
> });
>
> target.dispatchEvent(new CustomEvent('foo', {cancelable: true}));
>
> console.log("dispatch complete");
>
> ```
>
>
>
> On Chrome, I get:
>
>
>
> ```
>
> 1
>
> Uncaught 1
>
> 2; cancelling
>
> Uncaught 2
>
> dispatch complete
>
> then
>
> ```
>
>
>
> ...where the uncaught exception traces point to the `throw` line in the
> relevant event handler. Very nice. Note the synchronous processing. I
> should dive into the source, but clearly it's creating a job and running it
> synchronously (or code to that effect), and since the exceptions aren't
> handled by anything in the job, they get reported as unhandled.
>
>
>
> On Firefox, I get
>
>
>
> ```
>
> 1
>
> 2; cancelling
>
> dispatch complete
>
> then
>
> uncaught exception: 1
>
> uncaught exception: 2
>
> ```
>
>
>
> ...where the traces point to the `dispatchEvent` line. So it seems to
> store them up and then report them.
>
>
>
> Replicating the Firefox behavior in your own `dispatchEvent` function is
> fairly doable: Catch the exceptions, store them, and then fire them off
> asynchronously when done (https://jsfiddle.net/gwwLkjmt/):
>
>
>
> ```js
>
> class Publisher {
>
>     constructor() {
>
>         this.subscribers = new Set();
>
>     }
>
>     subscribe(f) {
>
>         this.subscribers.add(f);
>
>     }
>
>     trigger() {
>
>         const exceptions = [];
>
>         const event = {cancel: false};
>
>         for (const f of this.subscribers) {
>
>             try {
>
>                 f(event);
>
>             } catch (e) {
>
>                 exceptions.push(e);
>
>             }
>
>             if (event.cancel) {
>
>                 break;
>
>             }
>
>         }
>
>         for (const e of exceptions) {
>
>             setTimeout(_ => { throw e; }, 0);
>
>         }
>
>     }
>
> }
>
> const target = new Publisher();
>
> target.subscribe(e => {
>
>     console.log("1");
>
>     throw 1;
>
> });
>
> target.subscribe(e => {
>
>     console.log("2; cancelling");
>
>     e.cancel = true;
>
>     throw 2;
>
> });
>
> target.subscribe(e => {
>
>     console.log("3");
>
>     throw 3;
>
> });
>
> target.trigger();
>
> Promise.resolve().then(_ => console.log("then"));
>
> ```
>
>
>
> On Chrome, those traces point to our `setTimeout` line; on Firefox, they
> don't have a source. Not really ideal we have to wait for the next
> macrotask to report the exceptions, but it lets us run the handlers
> efficiently while still getting the engine to report the unhandled
> exceptions in its usual way. (Using `Promise.resolve().then(_ => { throw e;
> })` would at least put them on the task's microtask queue, but it would
> mean they'd be reported as unhandled rejections rather than unhandled
> exceptions.)
>
>
>
> I can't see how to replicate Chrome's behavior though.
>
>
>
> -- T.J. Crowder
>
>
>
> [1]:
> https://tc39.github.io/ecma262/#sec-continue-statement-runtime-semantics-evaluation
>
> [2]:
> https://tc39.github.io/ecma262/#sec-try-statement-runtime-semantics-evaluation
>
> [3]: https://tc39.github.io/ecma262/#sec-forbodyevaluation
>
>
>
> On Fri, Jun 23, 2017 at 10:20 AM, Andy Earnshaw <andyearnshaw at gmail.com>
> wrote:
>
> A long trip down a rabbit hole has brought me here. Long story short(ish),
> I was attempting to replicate how `EventTarget.prototype.dispatchEvent()`
> works in plain JavaScript code. A naive implementation (like Node's
> EventEmitter) would simply loop over any bound handlers and call them in
> turn.  However, this isn't very robust because one bound handler can
> prevent the rest from executing if it throws.
>
>
>
> DOM's dispatchEvent() doesn't have this problem.  Consider the following
> code:
>
>
>
> ```
> target = document.createElement('div');
>
> target.addEventListener('foo', () => { throw 1; });
>
> target.addEventListener('foo', () => { throw 2; });
>
> target.addEventListener('foo', () => { throw 3; });
>
> target.dispatchEvent(new CustomEvent('foo'));
>
> ```
>
>
>
> If executed in a browser, I see:
>
>
> > Uncaught 1
>
> > Uncaught 2
>
> > Uncaught 3
>
>
>
> Even though each one throws, they all still execute.  In our naive
> implementation, if you wrap each callback with a try/catch, errors thrown
> become handled, so the callback provider might not be aware of errors or it
> may be difficult to debug them without a stack trace. Global error handlers
> aren't triggered either.  If you swap out the catch block for a finally
> block (with either `continue` or some kind of recursive iteration), the
> errors aren't technically handled, but only that last one is considered
> "uncaught".
>
>
>
> I've observed this behaviour in current versions of Chrome, Firefox and
> Safari.  Does that mean the spec defines finally blocks to behave this way,
> or is it just an implementation-dependant behaviour they've all converged
> on?
>
>
> PS I realise that dispatchEvent's behaviour stems from it creating a new
> job for each handler function.  Interestingly, you can achieve something
> similar in browsers by appending a new script element per handler function
> to call it.  Not great for performance or achieving this transparently, but
> it works as a sort of proof-of-concept.
>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170623/b0d9d271/attachment-0001.html>


More information about the es-discuss mailing list