Description
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
-
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)
-
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 globalprocess
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 thePromise
instance. -
If there are no listeners for a
unhandledPromise
process event when one is emitted, node should print a warning to stderr. -
The
resolve
andreject
functions made available when constructing aPromise
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
- Added emitting of
unhandledPromise
events on exit.