Closed domenic closed 10 years ago
Interestingly both RSVP and Q trap "evil iterable" cases for .all
. Maybe that should be considered "argument validation" and trapped, instead of being allowed to bubble.
In 7e845e44045e4063c1e48cd3cfcab419620ae59b I shifted some bubbled exceptions to trapped rejections for argument validation in all/race. This moves the "evil iterable" cases into the rejections camp and makes all the bubbled exceptions stem from evil promise subclasses.
I believe this is a good place to be. Since this issue hasn't seen much interest, I will call it satisfactorily resolved, and close it for now. Let me know if it should be reopened for discussion.
The following are two consistent options I see for dealing with errors in promise-generating functions.
I might have missed it, but I haven't seen 2 discussed in these terms exactly. It seems most of the discussion in the past has revolved around what kind of error is being produced: Is it one that "looks like" an early error or does it "look like" the kind of error that should be swallowed? Maybe the thinking could be shifted to can this error always be detected synchronously?
For instance, when the then
property of the originator is accessed in UpdateDerived
, if it throws, it should result in a rejection because this happens in a queued up microtask (I think most people agree on that). However, the iterable
argument passed to Promise.all
can clearly be determined to be an invalid iterable synchronously, so perhaps it should result in a thrown exception.
So basically: Inside a microtask, reject; outside a microtask, throw. The language of the spec can be written such that sync internal functions always throw, but if they're called from inside a microtask the microtask will trap the exception and reject a promise instead.
Not sure I like 2 better than 1 at the moment... but it gives clear guidelines for how to proceed in any situation so that the choice of whether to throw or reject isn't arbitrary, and it sounds reasonable.
Thoughts?
@Nathan-Wall that is interesting, and I agree it does give somewhat consistent semantics. But I would rather produce a rejected promise for argument validation, for the reasons discussed in #24.
Ideally I would rather return a rejected promise for the "this
/this.constructor
-validation" situations, but on the other hand if your this
is messed up, you're going to have a hard time constructing a rejected promise to return in the first place, so that is my reasoning for drawing the line where I did.
I think all functions either synchronous or asynchronous should expose invalid arguments errors in same manner. It's rejection because of algorithm error (function cannot be executed), and that's in all cases synchronous.
In node.js all async functions throw on invalid arguments (same I believe is in all other async JavaScript API's that we have so far). It's expected behavior, and I don't remember anyone expecting something different. It's postponing such errors that may become confusing.
There are a number of pathological cases, mainly involving badly-behaved promise subclasses, that can cause unexpected state (e.g. a deferred whose [[Resolve]] is not callable).
Before the last day's worth of changes, these possibilities were either ignored (e.g. #51) or converted into rejections (see e.g. 420e9270429a8d9d6cac6ef5eee6b08bccd20493 for how that was done in
Promise.race
). After the recent set of changes, they are allowed to propagate as thrown exceptions via various ReturnIfAbrupt calls.I want to use this issue to discuss whether this new strategy of bubbling exceptions is correct, or if we should handle the pathological cases and transform them into appropriate rejections. Here are the ones I can find immediately, mostly by searching for "throw a TypeError" or "ReturnIfAbrupt" in the spec:
Current Spec Status
Cases that Bubble the Exception
Attempting to use any
Promise
static factory or combinator methods on athis
that returns a non-true-promise, e.g.Promise
static factory or combinator methods on athis
that is not a constructor.Promise
static factory or combinator methods on athis
that is a constructor but throws an error when passed a single function argument.Promise.prototype.then
on an object whoseconstructor
property is a throwing getter.Promise.prototype.then
on an object whoseconstructor
property is not a constructor.Promise.prototype.then
on athis.constructor
that is a constructor but throws an error when passed a single function argument.Any operation that needs to resolve a promise when used in the context of a constructor that passes a non-callable as the first argument to the
resolver
, e.g.Evil2
example, but for rejections.Any errors from the
resolve
andreject
functions themselves, e.g.Any errors while iterating through the iterables passed to
Promise.all
andPromise.race
, e.g.Cases that Trap the Exception
resolver
function passed tonew Promise
are converted into rejections. Previously covered in #23.Arguments
Arguments for Trapping
Arguments for Bubbling
Promise
class to be sure we don't just trigger another failure. So e.g.Evil.race
would return a rejectedPromise
, not a rejectedEvil
. That's weird.My Thoughts
I think the current status quo makes sense. It would have been nice to have completely asynchronous behavior, but it seems infeasible, in the face of all the things that can go wrong. I don't feel that strongly either way, though, so I am willing to be swayed. We could move one or two of the currently-bubbling cases into trapping, if you think they are more indicative of potential bad user input (see #24) than massive programmer failure.