A way of explicitly reporting exceptions

# Boris Zbarsky (8 years ago)

This most recently came up in the context of creating polyfills for requestAnimationFrame, but pretty much any self-hosted event-dispatching system runs into this problem.

Consider an API like this:

addListener: function(callback) {
  this.callbacks.push(callback);
}

notifyListeners() {
  // call all the callbacks here
}

Examples in the web platform include DOM event dispatch, mutation records, requestAnimationFrame, promise callbacks, and pretty much anything else that makes use of microtasks or enters user script multiple times off a single task. I would be somewhat surprised if other ES embeddings did not have similar constructs.

There are fundamentally two different implementation strategies for notifyListeners. The first looks like this:

for (listener of listeners) {
  listener();
}

and the second looks like this:

for (listener of listeners) {
  try {
    listener();
  } catch (e) {
    // Now what?
  }
}

The first strategy has the problem that a listener throwing an exception prevents later listeners from being notified, which is suboptimal. The second strategy has the problem that there is no way to route the caught exceptions through the embeddings normal exception-reporting mechanisms (window.onerror, console, etc). In particular, there is no way to explain in terms of the APIs available to the script the actual behavior of browser embeddings in situations like this.

So what I'd like to propose is some API that takes a value and routes it through the codepath uncaught exceptions get sent through. If such an API existed, one could implement notifyListeners as:

for (listener of listeners) {
  try {
    listener();
  } catch (e) {
    ourNewAPI(e);
  }
}

We can obviously add such an API in the web platform if we need to, but I wanted to check whether there's interest in having such an API on the language level in ES, given that other ES embeddings should have similar problems here.

Thoughts?

Note that so far I'm just asking whether this should be part of ES proper or not; we can decide on the exact naming and where to hang the API once we decide whether it's part of the language or specific to browser embeddings.

# Anne van Kesteren (8 years ago)

On Mon, Jun 23, 2014 at 8:54 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

So what I'd like to propose is some API that takes a value and routes it through the codepath uncaught exceptions get sent through.

I just filed www.w3.org/Bugs/Public/show_bug.cgi?id=26182 on HTML since it actually defines that code path (how it ends up in window.onerror and such). Cross-referenced this thread.

# Erik Arvidsson (8 years ago)

This is definitely something that I've needed before. The logical place for this is as part of the console API which is I believe has no spec (defacto standard).

# Tab Atkins Jr. (8 years ago)

On Mon, Jun 23, 2014 at 11:54 AM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

for (listener of listeners) { try { listener(); } catch (e) { // Now what? } }

Can't you just pass e into a setTimeout()'d callback, and rethrow it from there? Does that mess with the stack or something?

# Kevin Reid (8 years ago)

On Mon, Jun 23, 2014 at 12:08 PM, Tab Atkins Jr. <jackalmage at gmail.com>

wrote:

On Mon, Jun 23, 2014 at 11:54 AM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

for (listener of listeners) { try { listener(); } catch (e) { // Now what? } }

Can't you just pass e into a setTimeout()'d callback, and rethrow it from there? Does that mess with the stack or something?

Yes, but setTimeout may be less prompt than you want depending on the application (though another possibility is to use promises to queue it). You might also have an application-specific reason to do something after all the listeners (some kind of "buffer flush").

However, I'd like to propose a different facility: Instead of "catch and then report", have a "try and stop propagation but don't catch". This has a nifty advantage in debuggability: you can declare that a debugger's "stop on uncaught exception" should stop on such errors before the stack is unwound. This makes it much easier to debug errors in listeners, because you don't have to step through all other caught exceptions in order to stop on that exception.

This can be done without a new language construct by making it a primitive function:

callAndRedirectErrors(function () {
    listener();
});

which is equivalent to, in the absence of a debugger,

try {
    listener();
} catch (e) {
    <log e to console, etc>
}

In the presence of a debugger, it has the special behavior that any errors which would be caught by that catch block instead are stopped on if uncaught exceptions would be — that is, before the stack is unwound.

# Boris Zbarsky (8 years ago)

On 6/23/14, 3:08 PM, Tab Atkins Jr. wrote:

Can't you just pass e into a setTimeout()'d callback, and rethrow it from there? Does that mess with the stack or something?

It might for some thrown values in some implementations. It would definitely mess with reported exception ordering wrt actually uncaught exceptions... and in cross-realm cases has problems in terms of which realm's error-reporting machinery should be invoked.

Though I guess any API here has that last problem to some extent, actually. We might be able to hide it under the hood if the callback that threw is passed to the API along with the exception.

# Boris Zbarsky (8 years ago)

On 6/23/14, 3:35 PM, Kevin Reid wrote:

Yes, but setTimeout may be less prompt than you want depending on the application

Note that at least in some browsers window.onerror is called off an event loop task anyway.

However, I'd like to propose a different facility: Instead of "catch and then report", have a "try and stop propagation but don't catch".

We could do something like this, effectively keeping the reporting magic hidden as now. It doesn't go as far in terms of explaining the platform, etc, but does provide the minimal amount of leeway we need here.

This has a nifty advantage in debuggability: you can declare that a debugger's "stop on uncaught exception" should stop on such errors before the stack is unwound.

Note that such a facility would still fail in cases when a catch examines and then rethrows an exception, and in fact allows observably detecting whether an exception is caught and rethrown or just not caught. This actually affects one web platform spec algorithm (the node filter for treewalker/nodeiterator), which is currently specified to catch-and-rethrow.

# Kevin Reid (8 years ago)

On Mon, Jun 23, 2014 at 12:44 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

On 6/23/14, 3:35 PM, Kevin Reid wrote:

Yes, but setTimeout may be less prompt than you want depending on the application

Note that at least in some browsers window.onerror is called off an event loop task anyway.

Clarification: I meant how promptly the listener is invoked (independent of the error case).

This has a nifty advantage in debuggability: you can declare that a

debugger's "stop on uncaught exception" should stop on such errors before the stack is unwound.

Note that such a facility would still fail in cases when a catch examines and then rethrows an exception,

Yes, it would stop at the rethrow rather than the original throw. Doing more than that is hard.

and in fact allows observably detecting whether an exception is caught and rethrown or just not caught.

I did not intend it to do so. Could you explain?

(You can notice rethrows and things by inspecting the stack trace, but I assume that's not what you meant.)

# Boris Zbarsky (8 years ago)

On 6/23/14, 4:17 PM, Kevin Reid wrote:

Clarification: I meant how promptly the listener is invoked (independent of the error case).

Oh, yes. Calling the callback itself off a timeout is a non-starter. I assume that Tab was talking about rethrowing the exception off a timeout.

and in fact allows observably detecting whether an exception is
caught and rethrown or just not caught.

I did not intend it to do so. Could you explain?

Sorry, observably detecting via a debugger, not from the script itself.

(You can notice rethrows and things by inspecting the stack trace, but I assume that's not what you meant.)

That's an interesting issue too. Right now I believe browsers can internally rethrow an exception without changing its stack trace... But there is no way for script to do that.

# Boris Zbarsky (8 years ago)

On 6/23/14, 4:25 PM, Boris Zbarsky wrote:

That's an interesting issue too. Right now I believe browsers can internally rethrow an exception without changing its stack trace... But there is no way for script to do that.

Actually, I might be wrong there if the stack trace is captured at the time the Error object is constructed.

# Tab Atkins Jr. (8 years ago)

On Mon, Jun 23, 2014 at 1:25 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

On 6/23/14, 4:17 PM, Kevin Reid wrote:

Clarification: I meant how promptly the listener is invoked (independent of the error case).

Oh, yes. Calling the callback itself off a timeout is a non-starter. I assume that Tab was talking about rethrowing the exception off a timeout.

Correct; that's why I quoted the OP's second code example, which does a try/catch and then asks "now what?".

# Andrea Giammarchi (8 years ago)

I personally don't think it's DOM responsibility to clean up after "your" errors and I'd expect DOM to keep firing if a listener set by some other library or scope I don't know/own fails, as it has always been.

The only improvement I could think of is some info reachable through the event object, similar for .isDefaultPrevented() so that .hasUncaughtError() will return true and it's still developer responsibility to deal with such case when/if needed.

The most common Web scenario is where different libraries expect different behaviors from same handlers set on elements shared and reachable globally ... I'd rather not interfere with a generic listener and handlers stack I don't personally own from my code.

Last, but not least, I've seen only callback references in this topic, but if something changes, please remember there are tons of libraries out there that use the more handy EventListener#handleEvent() [1] call so context should be considered too whatever change will be decided.

Best

[1] www.w3.org/TR/DOM-Level-2-Events/events.html#Events

# Andrea Giammarchi (8 years ago)

just to clarify first sentence:

On Mon, Jun 23, 2014 at 1:35 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:

I personally don't think it's DOM responsibility to clean up after "your" errors and I'd expect DOM to keep firing if a listener set by some other library or scope I don't know/own fails, as it has always been.

I mean that you can always wrap your listeners inside a try/catch and deal with the case the best way you want.

Needing to change "the entire surrounding world" because of uncaught errors does not look the right thing to do in my opinion (delegating elsewhere problems caused by flaky listeners)

Again, just my opinion.

Best

# Boris Zbarsky (8 years ago)

On 6/23/14, 4:35 PM, Andrea Giammarchi wrote:

I personally don't think it's DOM responsibility to clean up after "your" errors

If I follow you correctly, what you're saying is that if there's some buggy ad on the web page that uses requestAnimationFrame and throws in the callback, then all other uses of requestAnimationFrame on the web page should stop working. Or keep working, depending on how they happen to race with the ad in terms of the ordering in the callback list. Or randomly work or not work on alternate page reloads.

I think convincing people to switch to such a behavior from the current is impossible. Even convincing them to implement such a behavior for new features is a touch sell.

But is that what you're really saying? I can't tell.

and I'd expect DOM to keep firing if a listener set by some other library or scope I don't know/own fails, as it has always been.

I'm really not sure what you're talking about here, even after your clarification mail.

The most common Web scenario is where different libraries expect different behaviors from same handlers set on elements shared and reachable globally ... I'd rather not interfere with a generic listener and handlers stack I don't personally own from my code.

OK. Who suggested interfering with anything?

What my mail said is that browsers currently implement a behavior that's impossible to implement in the ES language proper, and that it might be desirable to have a way to implement that behavior in ES proper.

# Andrea Giammarchi (8 years ago)

I am saying that if your requestAnimationFrame throws mine should keep working without problems as if your click handler fails mine should still work properly even if added after yours.

If the problem is being able to reproduce this in JS then there should be no ticket fired in HTML land and W3C since their behavior is actually expected and IMO correct since ever.

Here some background explored in 2009: dean.edwards.name/weblog/2009/03/callbacks-vs-events

It was that time everyone was trying to replicate events behaviors correctly so it might be useful for your case ;-)

Worth a read

# Boris Zbarsky (8 years ago)

On 6/23/14, 4:47 PM, Andrea Giammarchi wrote:

I am saying that if your requestAnimationFrame throws mine should keep working without problems

OK. That's the current behavior, and no one is proposing changing that.

The discussion is about allowing that while usefully reporting the thrown exception at the same time.

# Tab Atkins Jr. (8 years ago)

On Mon, Jun 23, 2014 at 1:47 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

I am saying that if your requestAnimationFrame throws mine should keep working without problems as if your click handler fails mine should still work properly even if added after yours.

Yes, that's the desired behavior and how everything in DOM works today, but which is difficult to reproduce in pure ES. Boris is suggesting we should have a way to reproduce this behavior easily in ES.

If the problem is being able to reproduce this in JS then there should be no ticket fired in HTML land and W3C since their behavior is actually expected and IMO correct since ever.

Note that this thread is in es-discuss, not a DOM mailling list, and Boris explicitly asked in the OP about whether this sort of thing should be addressed in ES.

# Andrea Giammarchi (8 years ago)

As I've said, I haven't seen anyone mentioning the handleEvent approach that's able to give you any info and error and stack you want whenever you want it inside any of its listeners

// recycle one handler
// to handle errors too
var handler = {
  handleEvent: function (e) {
    this[e.type](e);
  },
  click: function (e) {
    console.log('before');
    WHATEVER++
  },
  error: function (e) {
    this._errors.push(e);
    console.log(e.message);
  },
  _errors: []
};

document.documentElement.addEventListener(
  'click', handler
);

document.documentElement.addEventListener(
  'click', function () {
    console.log('after');
  }
);

window.addEventListener('error', handler);

now click the page and see how it works.

You, owning such object, can check it anywhere you want through this or direct reference and verify ._errors.length and behave accordingly.

# Andrea Giammarchi (8 years ago)

Yes Tab, I've seen first Anne's reply and felt the urge to jump into this worried somebody would have changed that.

OK, let's keep it here then ... <trollface>and use an offline DOM node to simulate the DOM behavior </trollface>

Jokes a part, I've read here and there that Rick W. is going to propose something close to EventListener so maybe this is a good opportunity to specify such behavior in there too?

Best

# Allen Wirfs-Brock (8 years ago)

It seems to me that the best way to do this in ES6 would be to run the call backs as individual Jobs (what until recently we were calling ES Tasks, people.mozilla.org/~jorendorff/es6-draft.html#sec-tasks-and-task-queues ). The mechanisms are already in the ES6 spec. so that any any unhanded exceptions that occur in a Job are handled in the common implementation defined manner.

What would have to be defined would be a new ES API for adding the invocation of a ES function as a pending job in a job queue. Maybe something like:

Function.prototype.callAsJob = function(...args) {
  $$EnqueueJob("UserJobs", ScriptEvaluationJob, args);  //handwave for calling ES internal abstraction operation
}

then notifyListners could just be:

for (listener of listeners) {
  listener.callAsJob();
}

and all the event handlers would be enqueue before any of them actually ran, which I believe is closer to what we want in terms of the ES concurrency model.

Of course, an ES level Jobs API could include other embellishments such as explicitly setting an unhandled exception handler for a Job, or associating completion of a Job with a Promise.

# Domenic Denicola (8 years ago)

It seems to me that the best way to do this in ES6 would be to run the call backs as individual Jobs

I definitely think there should be an API for queuing jobs. (At least, as long as jobs manifest as microtasks in Node.js and the browser; otherwise they are not as useful.)

However, I don't think that completely solves the problem here, as jobs are asynchronous, whereas event listeners are generally not.

# Allen Wirfs-Brock (8 years ago)

On Jun 23, 2014, at 2:23 PM, Domenic Denicola wrote:

It seems to me that the best way to do this in ES6 would be to run the call backs as individual Jobs

I definitely think there should be an API for queuing jobs. (At least, as long as jobs manifest as microtasks in Node.js and the browser; otherwise they are not as useful.)

However, I don't think that completely solves the problem here, as jobs are asynchronous, whereas event listeners are generally not.

I was under the impression that DOM triggered event listeners ran in their own turn and that this was the model we would want to generally apply to ES events.

# Andrea Giammarchi (8 years ago)

Within a list of callbacks the DOM behavior is that the error is not actually enqueued rather "spliced" in between.

with [a, b, c] and b throwing you'll have [a, b, bthrowed, c] plus this won't easily solve the problem: where does that error actually get triggered ?

the DOM use global error handler on window object which also mean cross realm is not an option but considering cross realm has never been a server side issue (so far, not sure how clusters behaves in these cases) would it make sense to have a global listener in JS too?

I don't think so, so yes, I start understanding what was the initial problem and no, I've no idea how to solve it.

Best

# Domenic Denicola (8 years ago)

From: Allen Wirfs-Brock <allen at wirfs-brock.com>

I was under the impression that DOM triggered event listeners ran in their own turn

Nope.

and that this was the model we would want to generally apply to ES events.

Maybe (probably?); there are strong arguments on both sides. We've certainly stuck with that story for anything vaguely event-like so far, viz. promises and Object.observe.

# Boris Zbarsky (8 years ago)

On 6/23/14, 2:54 PM, Boris Zbarsky wrote:

Note that so far I'm just asking whether this should be part of ES proper or not; we can decide on the exact naming and where to hang the API once we decide whether it's part of the language or specific to browser embeddings.

I'd like to refocus on this. Is this something people want in ES proper, in which case we should work up a strawman, etc, or is this something I should talk to the DOM/WebAPI folks about? The discussion seems to have veered a bit into how such functionality could be done, which is useful for making that sort of decision, but I'd really rather not rathole on deciding exactly what the API should look like when we're not even sure we want it at all.

So to be clear: is this something that people feel should be part of ES-the-language?

# C. Scott Ananian (8 years ago)

I am ambivalent about where it goes; it depends to some degree with how the feature is implemented.

setTimeout(function() { throw e; }, 0); is ugly, but it seems to have most of the behavior you want. There are similar mechanisms with promises and jobs that could be used. I believe Allan said that the "use the standard uncaught exception handler" behavior is already in the standard. If we're talking about tweaks to make this approach more palatable (Promise.throw(e) ?) then the ES6 spec is appropriate.

Alternatively, one could view this as part of the console functionality, since what seems to be wanted is simply easier access to the debugging capabilities of modern web consoles, like the already-existing console.trace(message) which prints message along with a stack trace. (And some people want to be able to pass additional flags to have certain special behaviors in a debugger.) That would belong on the DOM side (or wherever the cross-platform console spec is kept).

Finally, it hasn't been discussed much, but some platforms provide explicit access to the 'uncaughtException' handler. In python this is sys.excepthook and in node this is an uncaughtException event on the process object. If we wanted to standardize something like that I'm not sure which spec that would belong in.

# Domenic Denicola (8 years ago)

So to be clear: is this something that people feel should be part of ES-the-language?

I definitely think it should be part of ES-the-language. I agree with Scott that to some extent it depends on the shape the feature takes, but IMO this is a fundamental capability of modern language runtimes, and it should work at the language level. To me it seems of the same importance as try/catch/finally.

I'd be interested in collaborating on designing such a language extension.

# Jason Orendorff (8 years ago)

On Tue, Jun 24, 2014 at 8:38 AM, C. Scott Ananian <ecmascript at cscott.net> wrote:

I am ambivalent about where it goes; it depends to some degree with how the feature is implemented.

setTimeout(function() { throw e; }, 0); is ugly, but it seems to have most of the behavior you want.

What. Strongly disagree. Timing matters!

  • Error output should appear in the correct order relative to other output. Shuffling output confounds printf-style debugging (which is hardly rare in web dev).

  • In particular, if this error causes other errors, it should be reported before the others, so as to avoid violating causality.

  • Later side effects against e shouldn't affect the error message that's displayed.

That's only taking diagnostic output into account. But it seems like timing would be just as important for the purposes of window.onerror etc.

[...] Finally, it hasn't been discussed much, but some platforms provide explicit access to the 'uncaughtException' handler.

+1. This feature would be a complement to what bz proposed. Either feature could be added and used independently from the other.

# C. Scott Ananian (8 years ago)

Note first that I was trying to summarize the broad dimensions of the alternatives discussed, not outline spec-quality proposals.

On Tue, Jun 24, 2014 at 2:43 PM, Jason Orendorff <jason.orendorff at gmail.com>

wrote:

On Tue, Jun 24, 2014 at 8:38 AM, C. Scott Ananian <ecmascript at cscott.net> wrote:

I am ambivalent about where it goes; it depends to some degree with how the feature is implemented.

setTimeout(function() { throw e; }, 0); is ugly, but it seems to have most of the behavior you want.

What. Strongly disagree. Timing matters!

Yes. Hence "most", etc. I was eliding the whole discussion of synchronous-vs-asynchronous, etc. See other messages by other people for that. I was just noting that one broad direction was to build on the existing handling of "uncaught exceptions in jobs". There are lots of details here, timing included, as others have discussed. But fundamentally this option is "provide direct access to the already existing job/task/turn mechanism, using the uncaught exception handler already specified there". [I'm going to call this option "a".]

Finally, it hasn't been discussed much, but some platforms provide explicit access to the 'uncaughtException' handler.

+1. This feature would be a complement to what bz proposed. Either feature could be added and used independently from the other.

We'll call this option "c", after the ordering in my original message. Note that I elided lots of details here, as well. Python, for example, allows you to access sys.excepthook as an ordinary function object, so you can save a reference and invoke it later, add a chained handler that defers to the stock handler in certain circumstances, etc.

Node fires an event for an uncaught exception; I don't think that it is straightforward to reach inside the handler chain for this the way you do in python. You can have multiple handlers, though! And the handlers are scoped to a specific process object.

Several commenters on this thread expressed the desire to log stack traces to console/set breakpoints/etc without halting execution, for example when dispatching handlers. Only something based on console.trace (which we'll call "option b") seems to have that ability at the moment -- if you "invoke the default uncaught exception handler" (either via a synchronous job/task/turn mechanism or by direct access to the uncaught exception handler), you have to expect that default handler may halt execution.

Which is just to say that there are lots of options here and I'm not sure exactly which spec it belongs with. I fear that we will end up with three different overlapping-but-slightly-differing mechanisms, based on (a) jobs/tasks/turns, (b) the console object, and (c) some sort of platform-specific access to the default uncaughtException handler. Perhaps the solution is to work on this initially as a new "cross-platform" spec, akin to "console", which can tackle all three aspects at once.

# Axel Rauschmayer (8 years ago)

[...] Finally, it hasn't been discussed much, but some platforms provide explicit access to the 'uncaughtException' handler.

+1. This feature would be a complement to what bz proposed. Either feature could be added and used independently from the other.

I’m not completely sure, but Angular’s work on Zones may apply here, too, to configure when and what to catch: angular/zone.js

# Boris Zbarsky (8 years ago)

On 6/24/14, 9:51 AM, Domenic Denicola wrote:

I'd be interested in collaborating on designing such a language extension.

I think I would too; I'm just not sure about availability.

# Domenic Denicola (8 years ago)

From: Boris Zbarsky [mailto:bzbarsky at MIT.EDU]

On 6/24/14, 9:51 AM, Domenic Denicola wrote:

I'd be interested in collaborating on designing such a language extension.

I think I would too; I'm just not sure about availability.

For posterity, I tried to replicate this functionality using recursive try-finally. It does not quite work: Chrome reports 1, 2, error, 3 for EventTarget, with 1, 2, 3, error for my custom try-finally version.

(Firefox seems to report error, 1, 2, 3 in both cases, which I am guessing is a result of some implementation detail of the console.)

So indeed this is not something you can do with JS as it stands.