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:[email protected]] On Behalf Of T.J. 
Crowder
Sent: Friday, June 23, 2017 07:01
To: Andy Earnshaw <[email protected]>
Cc: es-discuss <[email protected]>
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 
<[email protected]<mailto:[email protected]>> 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
[email protected]<mailto:[email protected]>
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to