-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"thenable" with then
getter that throws
#88
Comments
My current strategy in when.js is to reject the assimilating promise if an exception is thrown when trying to access Also, see @domenic's point here about just how much trouble a |
One way of dealing with it is code like this: try {
if (x) {
var then = x.then;
if (typeof then === "function") {
then.call(x, resolvePromise, rejectPromise);
} else {
fulfillPromise(x);
}
}
} catch (e) {
rejectPromise(e);
} Notable is the use of Specifying this seems like it would be really hairy though. But probably important. |
Yeah, that's basically what I've started doing in when.js. The important thing, as your code points out, is that you should only access |
Although hmm, treating it as a non-thenable seems more accurate. if (x) {
var then;
try {
then = x.then;
} catch (e) {
fulfillPromise(x);
}
if (typeof then === "function") {
try {
then.call(x, resolvePromise, rejectPromise);
} catch (e) {
rejectPromise(e);
}
} else {
fulfillPromise(x);
}
} |
The ES5 spec actually deals with this kind of stuff a lot. E.g. in
|
Heh, right. |
A couple thoughts to support why we might treat it as a non-thenable:
|
And a couple thoughts of why rejecting might be better:
|
@erights, do you have any comments? Seems like the kind of situation you consider all the time :) |
This helps specify how you should get a reference to the `then` method, test it, and then call that same reference, as discussed in #88. I think it also makes the spec rather more elegant.
@domenic yes. Thanks for checking! Function.prototype.call.call(then, thenable, resolve, reject) isn't safe either because Function.prototype.call or call.call may have been corrupted. See http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming Good points about getting .then only once, and about the possibility that getting it might itself throw. I have just updated makeQ.js to correct all three issues. See https://code.google.com/p/es-lab/source/browse/trunk/src/ses/makeQ.js#211 and let me know if you see any problems with it. I am taking the philosophy stated above by @briancavalier explaining why rejecting is better. Essentially, don't drop thrown errors if there's a natural promise to break with that error as the reason. I see the tension, but I think this is better than treating the value as a non-thenable. This is also the right decision for making the assimilation pattern useful as an async analog of Q.promise:
If the body above throws, we'd want it to reject p, just as we would if the factory function passed to Q.promise throws. |
Thanks, @erights.
For sure. Grabbing/binding/ucurryThis'ing early is the best thing.
Looks good to me. Accessing |
@erights Sounds good, and thanks for chiming in! Just to clarify, though:
This makes sense, but on the other hand, it wasn't quite the example in question. What about this example? let badThing = { get then() { throw new Error("argh!"); };
var p = Q(badThing); Should |
@domenic Right, that is a different example. But the governing principle is the same -- don't throw away errors is there's a natural promise to reject with that error as the reason. p should be rejected with "argh!". My corrected makeQ code I link to above does that. |
OK, cool. I'll update #89 to have that behavior then. If anyone has objections, now's the time! (Although I would imagine it's us three who are most concerned about edge cases like this.) |
No objections here :) |
I'm still on the side that rejecting is the right thing, but feeling a little less certain about it. Here's where my head is at the moment. First, I'm feeling a little stuck on this:
This makes a lot of sense to me. The problem is that the thrown exception has to be silenced :/ @novemberborn's point in #87 that observation shouldn't cause a rejection is a good one. However, the problem is that in the ES5-and-later JS world, observation can cause an exception. If we maintain the parallel between thrown exceptions and rejections, that implies observation can cause a rejection. And practically speaking, we must observe In sync code, a throwing getter obviously will prevent subsequent sync code from executing, and will propagate up the stack until the exception is caught. If we want to parallel that in async, observing a thing should be allowed to produce a rejection that propagates until handled. Unfortunately, we're observing it inside the promise machinery, which isn't really obvious to the user. Another thought is that if there are no reasonable use cases (I have no idea if there are or aren't) for pathological throwing thenable doppelgangers, maybe we should prevent them from ever entering the promise machinery at all, i.e. reject these insane things as soon as we see them. |
@briancavalier For what it's worth I agree with both sides of your argument. It seems clearly "correct" from a first principles point of view that these things are not thenables, and thus should be treated as non-thenables and just passed through as fulfillment values. But then again, silencing the error is unfortunate and if we try and draw a parallel with sync functions, rejecting seems like a reasonable reaction. So I'm OK either way.
I think this is analogous to observing
@ForbesLindesay pointed out a pretty good one: proxy objects which throw when you try to access properties that are not defined on them. Again I think the |
With last observations made by @domenic, it is clear to me that rejecting is the right thing to do.
This is also true on observing
And, if I am right, spec already expects from users to embrace some responsibility on what is resolved (thenables), and to wrap when there is no enough knowledge about results. |
This helps specify how you should get a reference to the `then` method, test it, and then call that same reference, as discussed in #88. I think it also makes the spec rather more elegant.
See #87. We probably need to specify how to deal with this situation for assimilation.
The text was updated successfully, but these errors were encountered: