Re: [whatwg] An API for unhandled promise rejections

The OP proposal.

> -----Original Message-----
> From: whatwg [mailto:whatwg-bounces@lists.whatwg.org] On Behalf Of
> Elliott Sprehn
> Sent: Monday, May 18, 2015 19:23
> To: Domenic Denicola
> Cc: WHATWG; Boris Zbarsky; Brian Terlson
> Subject: 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:26:37 UTC