Skip to content

Proposal for more correct detection and reporting of unhandled promise #31148

Closed
@mike-marcacci

Description

@mike-marcacci

Background

There exists a contingent of the community who wants node's default behavior changed to crash as soon as a promise is rejected if an error handler is not yet been attached to the promise. This breaks one of the most important design features of promises: allowing predictable execution regardless of when a handler is attached (either before or after a promise is resoved/rejected; and before or after any other handlers are attached). This guarantee of promises protects them against many of the hard-to-reproduce timing bugs I often see associated with event emitters, streams, and other concurrency patterns. Accordingly, I'm very resistant to seeing javascript get strongarmed out of this feature by one runtime.

I've made my opinions quite clear in my comments on this issue, but the actual implementation described there lacks clarity and has changed multiple times throughout the issue's history.

I would like to propose a very specific course of action here, and I would also like to encourage those with differing perspectives to create standalone issues that describe specific alternative plans so that we (the broader community) can more clearly identify the consequences of each.

Proposal

  1. The "end of life" for an instance of Promise occurs either:

    • when the promise instance is garbage collected
    • when the application exits normally (ie. when the exit event is emitted)

  2. At the end of life for each instance of Promise, node should check for the presence of handlers for the following states:

    • fulfilled
    • reject

    If either of these states have no handlers, node should emit an unhandledPromise event on the global process object with a payload object containing the following fields:

    state

    A string, either "pending", "rejected", or "fulfilled"

    value

    The value/reason of a fulfilled/rejected promise, or undefined.

    onFulfilled

    An array of handlers for the fulfilled state.

    onRejected

    An array of handlers for the rejected state.

    stack

    A string in the format of Error.prototype.stack tracing the construction of the Promise instance.

  3. If there are no listeners for a unhandledPromise process event when one is emitted, node should print a warning to stderr.

  4. The resolve and reject functions made available when constructing a Promise must internally keep a weak reference to the promise instance. This prevents undesired retention of the promise which could delay or circomvent this intervention.

Important Features

There are several important features and goals guiding this proposal:

It must not break promises.

Most importantly, this does not change the semantics of javascript promises or invalidate any well-established patterns.

It must not encourage self-defeating workarounds.

By providing a mechanism for applications to programattically ignore specific warnings, we avoid encouraging libraries to modify promises in ways that make potentially problematic scenarious undetectable by node (such as adding noop handlers like promise.catch(() => {})).

It should identify problematic conditions as early as possible.

Promises intentionally don't expose a mechanism for checking an instance's internal state. Therefore, any promise that has reached its end of life has all the handlers that it will ever have.

The primary concern motivating any special handling of promises is that rejections will go unhandled. Any promise that has reached its end of life without a rejection handler (regardless of its state at the time it reached its end of life) is vulnerable to this.

It must provide developers sufficient information to track down potential problems.

A stack trace to the construction of the promise provides a developer everything necessary to identify a potential problem or to selectively ignore safe discarding of unhandled promises.

Possible Problems

Creating a stack trace could have a non-trivial performance penalty. Some optimizations may mitigate most of this:

  • Discard stack information as soon as a handler is added to both final states.
  • Lazily construct the string representation as a getter.

See Also

Edits

  1. Added emitting of unhandledPromise events on exit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    promisesIssues and PRs related to ECMAScript promises.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions