Re: [whatwg] An API for unhandled promise rejections

What of the many things in that email are you considering?

On Mon, May 18, 2015 at 3:49 PM, Domenic Denicola <d@domenic.me> wrote:

> Ping. We're considering implementing this in Chrome and it would be
> helpful to get a sense if other vendors support this.
>
> > -----Original Message-----
> > From: whatwg [mailto:whatwg-bounces@lists.whatwg.org] On Behalf Of
> > Domenic Denicola
> > Sent: Monday, February 9, 2015 20:45
> > To: WHATWG
> > Cc: Petka Antonov
> > Subject: Re: [whatwg] An API for unhandled promise rejections
> >
> > An update on this: although the conversation somewhat fizzled here, in
> io.js
> > (Node.js fork) something very similar is landing and I'm trying to guide
> it
> > toward being reasonably compatible with browsers [1]. Additionally
> several
> > promise libraries have implemented similar hooks (see [2] and links in
> the
> > comments; also of interest is the analysis of how promise-using
> libraries use
> > these hooks).
> >
> > The community largely settled on unhandledRejection/rejectionHandled,
> > instead of hijacking the error event like the original post below
> proposes.
> > Which is not to say that we have to respect that in browsers, but it's a
> data
> > point to consider.
> >
> > One interesting question that came up is the exact timing of the
> > unhandledRejection event. In my proto-spec at [3] I proposed queuing a
> > separate notify-rejected task for each rejection, largely because it
> seemed
> > the easiest thing to spec. We now have some experience from the field.
> The
> > implementation at [1] considered a few approaches and cycled through
> > implementing some subset of them to make a progressively-larger set of
> > tests pass:
> >
> > - Queue a single task
> > - Queue a separate task per rejection (as in proto-spec)
> > - Queue a microtask that occurs after all other microtasks
> > - Queue a task that occurs after all other tasks
> >
> > Hopefully Petka, CC'ed, can correct me if I misstated these and fill in
> any
> > details I missed.
> >
> > In general I am in favor of pushing off notification as long as possible
> to give
> > more time for the rejection to potentially become handled (and thus
> > decrease false positives). From this perspective either separate task per
> > rejection or after-all-others task seems good. I was hoping to get a web
> > platform perspective on what sounds good and would be implementable?
> > For example I think the after-all-others-task can be specced with a new
> task
> > source that HTML mandates is drained after all others, right?
> >
> > Anyway, I mostly just wanted to give people an update and show that we're
> > prototyping this in io.js. Hopefully the interchange of ideas here can
> help
> > push the progress in browsers too.
> >
> >
> > [1]: https://github.com/iojs/io.js/pull/758
> > [2]: https://gist.github.com/benjamingr/0237932cee84712951a2
> > [3]: https://gist.github.com/domenic/9b40029f59f29b822f3b#promise-error-
> > handling-hooks-rough-spec-algorithm
> >
> >
> > -----Original Message-----
> > From: Domenic Denicola
> > Sent: Friday, September 12, 2014 14:34
> > To: WHATWG
> > Subject: An API for unhandled promise rejections
> >
> > ## Problem
> >
> > A common desire in web programming is to log any uncaught exceptions
> > back to the server. The typical method for doing this is
> >
> >     window.onerror = (message, url, line, column, error) => {
> >       // log `error` back to the server
> >     };
> >
> > When programming asynchronously with promises, asynchronous
> > exceptions are encapsulated as _rejected promises_. They can be caught
> > and handled with `promise.catch(err => ...)`, and propagate up through an
> > "asynchronous call stack" (i.e. a promise chain) in a similar manner to
> > synchronous errors.
> >
> > However, for promises, there is no notion of the "top-level" of the
> promise
> > chain at which the rejection is known to be unhandled. Promises are
> > inherently temporal, and at any time code that has access to a given
> promise
> > could handle the rejection it encapsulates. Thus, unlike with synchronous
> > code, there is not an ever-growing list of unhandled exceptions: instead,
> > there is a growing and shrinking list of currently-unhandled rejections.
> >
> > For developers to be able to debug promises effectively, this live list
> of
> > currently-unhandled rejections certainly needs to be exposed via
> developer
> > tools, similar to how devtools exposes the ever-growing list of unhandled
> > exceptions (via console output). However, developer tools are not
> sufficient
> > to satisfy the telemetry use case, i.e. the use case which is currently
> handled
> > via `window.onerror` for synchronous code.
> >
> > ## Proposed Solution
> >
> > We propose that
> >
> > 1. `window.onerror` be extended to handle the rejected-promise use case,
> > notifying about any promises that, "at the end of the task queue",
> contain
> > rejections that are not yet handled; and 2. A new hook,
> > `window.onrejectionhandled`, be added, to notify when (or if) such
> > rejections eventually become handled.
> >
> > By "at the end of the task queue" I mean that upon a promise being
> rejected
> > with no handlers, we would queue a task to fire an error event; however
> if a
> > handler is then attached to a promise in the meantime, a flag would be
> set so
> > that when the task executes nothing actually happens.
> >
> > ### Developer Experience
> >
> > In terms of developer experience, the result is that if a promise is
> rejected
> > without any rejection handler present, and one is not attached by "the
> end
> > of the event loop turn", the resulting `(message, url, line, column,
> error,
> > promise)` tuple will hit `window.onerror`. If the developer subsequently
> > attaches a rejection handler to that promise, then the `promise` object
> will
> > be passed to any handlers for the `rejectionhandled` event.
> >
> > As usual, if one or both of these events is missing listeners, nothing
> will
> > happen. (In this case, the developer likely does not want to do
> telemetry on
> > errors, but instead will be availing themselves to the devtools.)
> >
> > A robust error-reporting system would use `rejectionhandled` events to
> > cancel out earlier `error` events, never displaying them to the person
> reading
> > the error report.
> >
> > ### Specification Details
> >
> > We would extend [`ErrorEvent`](http://www.whatwg.org/specs/web-
> > apps/current-work/multipage/webappapis.html#the-errorevent-interface)
> > and `ErrorEventInit` with a `promise` member. Similarly, we would extend
> > the  [`OnErrorEventHandlerNonNull`](http://www.whatwg.org/specs/web-
> > apps/current-
> > work/multipage/webappapis.html#onerroreventhandlernonnull) callback
> > type to take as its last argument that same promise. In both cases, the
> > promise would be `undefined` for synchronous errors.
> >
> > We would add a new event to the global, named `rejectionhandled`, along
> > with a `RejectionHandledEvent` class that contains only a `promise`
> member.
> >
> > We would need to hook into rejecting promises and `then`-ing promises,
> and
> > track unhandled rejections:
> >
> > * When a promise is rejected, if it has no handlers, we would queue a
> task to
> > potentially-fire-an-error.
> > * When a promise is `then`'d (either by user code or by the spec's
> chaining
> > mechanisms) but the rejection has not yet been reported, we would set a
> > flag saying "don't fire that error after all."
> > * When the task is executed, if that flag is still unset, we would then
> fire the
> > appropriate `error` event.
> > * If a promise is `then`-ed in such a way as to handle the rejection,
> but that
> > promise had previously been reported as an unhandled rejection, we would
> > need to fire the appropriate `rejectionhandled` event.
> >
> > I can go into details on how to modify the promises spec to have these
> > hooks, if desired, as well as how HTML would exploit them to maintain the
> > appropriate list and report it at the end of the task queue. I can also
> help with
> > the spec work here, on both the ES side and the HTML side, if desired.
> >
> > ### Potential Variants
> >
> > The `error` event and its idiosyncratic handler are not the best possible
> > extension points. We may be better off with a separate
> `unhandledrejection`
> > event (or, more accurately and as [popular
> > libraries](https://github.com/petkaantonov/bluebird/#error-handling)
> call it,
> > `possiblyunhandledrejection`). We could even unify on a single event
> class
> > used for both, e.g. `PromiseRejectionEvent` with members `promise` and
> > `reason`. This improves clarity and reduces piling kludges on top of
> > `window.onerror`, but requires any existing telemetry code to upgrade to
> > support the new event.
> >
> > I personally think this is a better solution, both because it has less
> kludges
> > and because I can see server telemetry tools that aren't upgraded to
> > recognize the new duality becoming overwhelmed with useless `error`
> > events that are later canceled by `unhandledrejection` events they are
> > unaware of. That is, if you try to plug asynchronous errors into your
> existing
> > telemetry systems, you will be pulling your hair out over spurious, and
> > sometimes hard-to-reproduce, errors in your logs. But other members of
> the
> > Chrome team feel strongly about re-using onerror and I am happy to let
> this
> > play out in the real world.
> >
> > Note that we are proposing this for the web, and not for ES, because the
> > web has `window.onerror` (not to mention an event system) already. A
> > more generic unhandled-rejection-tracking mechanism for all ES
> > environments might be something like an `Object.observe`able
> > `Promise.unhandledRejections` array, but that discussion can be left for
> > another time.
> >
> > ## Implementer Interest
> >
> > Chrome is interested in implementing this ASAP. I'm broaching the idea
> for
> > the first time publicly to hopefully get other implementer interest or
> at least
> > rough consensus :).
>

Received on Monday, 18 May 2015 23:24:24 UTC