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 <[email protected]> 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:[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]> > 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] > https://mail.mozilla.org/listinfo/es-discuss > > >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

