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

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

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

Andy Earnshaw-2
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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

T.J. Crowder-2
> 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


On Fri, Jun 23, 2017 at 10:20 AM, Andy Earnshaw <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Domenic Denicola

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:[hidden email]] On Behalf Of T.J. Crowder
Sent: Friday, June 23, 2017 07:01
To: Andy Earnshaw <[hidden email]>
Cc: es-discuss <[hidden email]>
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

 

 

On Fri, Jun 23, 2017 at 10:20 AM, Andy Earnshaw <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

 


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Andy Earnshaw-2
In reply to this post by T.J. Crowder-2
> I think that's the answer to your question about `finally`.

Indeed it is, thank you.  I'd read before about propagation of exceptions but I was having trouble pinning down where this happens in the spec text.

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.

The other downside is that "break on exceptions" in the debugger will break in the wrong place with the wrong stack trace (you'd have to break on caught exceptions, which includes all the noise from other caught exceptions).

On Fri, 23 Jun 2017 at 12:01 T.J. Crowder <[hidden email]> wrote:
> 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


On Fri, Jun 23, 2017 at 10:20 AM, Andy Earnshaw <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Andy Earnshaw-2
In reply to this post by Domenic Denicola
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 <[hidden email]> 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:[hidden email]] On Behalf Of T.J. Crowder
Sent: Friday, June 23, 2017 07:01
To: Andy Earnshaw <[hidden email]>
Cc: es-discuss <[hidden email]>
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

 

 

On Fri, Jun 23, 2017 at 10:20 AM, Andy Earnshaw <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

 


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Sebastian Malton
This looks quite cool but I have a question. Would fixing this allow for non- blocking calls called within a try / catch when throwing an error be caught by that catch statement? 

Sent: June 23, 2017 10:53 AM
Subject: Re: Are thrown errors in a try block considered to be handled even if there's no catch block?

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 <[hidden email]> 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:[hidden email]] On Behalf Of T.J. Crowder
Sent: Friday, June 23, 2017 07:01
To: Andy Earnshaw <[hidden email]>
Cc: es-discuss <[hidden email]>
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

 

 

On Fri, Jun 23, 2017 at 10:20 AM, Andy Earnshaw <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

 


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Boris Zbarsky
In reply to this post by T.J. Crowder-2
On 6/23/17 7:01 AM, T.J. Crowder wrote:

> 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.

I should note that if you add this to your testcase:

   window.onerror = function(...args) {
     console.log(...args);
   }

then you will see something like this in Firefox:

1
uncaught exception: 1 foo.html 20 1 1
2; cancelling
uncaught exception: 2 foo.html 20 1 2
dispatch complete
then
uncaught exception: 1
uncaught exception: 2

where those first two "uncaught exception" lines are the logs from the
error handler.  So for the parts that are web-observable, Firefox does
the error reporting synchronously.

The rest of what you see is because the console API and the internal
error reporting use slightly different mechanisms for notifying about
new messages: the former does it immediately and the latter does it
after an event loop turn, because it's working with a threadsafe logging
facility that always handles things via a job queued on the main event loop.

> 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/):

That won't give you the right onerror behavior.

-Boris
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

T.J. Crowder-2
On Fri, Jun 23, 2017 at 5:39 PM, Boris Zbarsky <[hidden email]> wrote:

> I should note that if you add this to your testcase:
>
> ...
>
> then you will see something like this in Firefox:

Cool, thanks, I never got around to trying that.

> That won't give you the right onerror behavior.

Indeed not, I should have said "near-replicating." That's presumably not the only minor difference between them...

-- T.J. Crowder


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Boris Zbarsky
In reply to this post by Boris Zbarsky
On 6/23/17 12:39 PM, Boris Zbarsky wrote:
> The rest of what you see is because the console API and the internal
> error reporting use slightly different mechanisms for notifying about
> new messages:

I filed https://bugzilla.mozilla.org/show_bug.cgi?id=1375899 to
hopefully align these more, fwiw.

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