-
Notifications
You must be signed in to change notification settings - Fork 4
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
Strawman: Promise Creation and Resolution API #10
Comments
Made a couple very minor edits, mainly reducing bullet nesting. |
Open issue: It seems clear how to handle the case of |
Do we want to trap errors in the init function? It looks like we should be letting it break early. |
I think we want to trap the errors either way. If resolve has already been called, we should silence the error. If resolve has not been called we should transform the error into a rejection. |
@ForbesLindesay Interesting. My knee jerk was the opposite: if Hmmm, hard to know which is better: favoring the |
Errors are not something to ignore. I'd say we treat it the same way we do |
Perhaps "new Promise(resolutionFunction)" bullet 5 can be rewritten to something like:
Hmmm, or maybe it's better to leave it spelled out fully. |
Ah, I hadn't considered the fact that the entire function is synchronous, I like @briancavalier's initial instinct to ignore the resolve call and just turn it into a rejection if an exception was thrown. The only downside is that we can't do that when the resolution/rejection is a asynchronous. |
I am glad we are moving forward with this. I am generally happy with the idea here, and am even coming around to keeping the "resolve" verb, in favor of renaming the adjective. As long as we can get Mark Miller on board with a new adjective too, we're in good shape. But let's put aside the naming, as you said. I don't like Furthermore, I think the algorithm for As for the thrown-exception vs. // should be fulfilled with 5
var p = new Promise(function (resolve) {
resolve(5);
throw new Error();
});
// should be rejected with the error
var p = new Promise(function (resolve) {
setTimeout(resolve.bind(null, 5), 100);
throw new Error();
}); This isn't quite the same as @briancavalier's last comment, though. In particular " It's a bit annoying that we can't allow returning from the function to resolve with that value, since it implicitly returns |
Ah, it just occurred to me why exactly I find var fulfilledPromise = new Promise(function (resolve) { resolve(5); });
var rejectedPromise = new Promise(function () { throw 5; }); or even more bluntly, Promise.reject = function (reason) {
return new Promise(function() { throw reason; });
}; Thus the truly minimal proposal doesn't include it at all, leaving me in a good position to be in agreement with the minimal and strongly in favor of |
Cool, we seem to be converging on at least Promise.reject @domenic You're right, new Promise(function(resolve) {
setTimeout(function() {
resolve(new Promise(function() {
throw new Error();
}));
});
}); Nobody will want to type that. I def don't want to type that :) Implementations will inevitably provide sugar for it, so why not just include some now so that at least the implementations will all have the same sugar. To quote @unscriptable: "It's like ice cream, just put the sugar in there cuz nobody wants it without it" So, even though it's not absolutely minimal, I feel like we should specify either: 1) a terse way of creating an eventually rejected promise, which can be passed to fulfill(verbatim) and reject(verbatim) I've documented my reasons here and here for why I feel leaking promises to handlers is dangerous. Mark Miller and @kriskowal have expressed similar concerns, at least about resolutionFunction return values This would be a very nice parallel with |
Promise.reject I think your code can be safely simplified to: new Promise(function() {
throw new Error();
}); since new FooPromise(function (resolve) {
realAsyncOp(function (err) {
resolve(Q.reject(err));
});
}); If anything I think it would also be worth being able to do: new FooPromise(function (resolve) {
realAsyncOp(function (err) {
resolve.reject(err);
});
}); as that will save the creation of an un-necessary promise (and therefore may perform significantly better). resolutionFunction return values We could handle resolutionFunction return values that are promises (but not that are values). This would be helpful for the fairly common scenario: function doSomething() {
return new Promise(function () {
var foo = someSyncOperationThatMightThrow();
return asyncOperation(foo);
});
} You need to do the sync stuff inside the promise wrapper so you can catch thrown exceptions, but after that you can just return a promise. |
I feel strongly that we need In essence, we have painted ourselves into a corner, and need to start living with it. That corner being the usage of polymorphic We can encourage the use of You could try to introduce a fourth verb, call it I think the best we can get at this stage is a polymorphic I'm on the fence about allowing return values that are promises to have an effect. It is nice, but it is also misleading, and doesn't fit well with one's expected intuition, IMO. |
We must ignore the return value of the promise constructor. Allowing a promise return value would imply using resolve(returned), which would imply that omitting a return value would resolve the promise to undefined, which would clearly not fly. To make it work, we would have to special case either |
@kriskowal I agree, I'm not convinced by return values either, I was just making sure we considered something that could work (and would have some value). I'm not convinced we need a verbatim As for reject, that boat sadly has sailed. Unless we can find another nice word that means |
A passing comment to give this discussion a little context. I recently had an interesting talk with a friend who writes Haskell in which he told me that monads have two distinct operations:
I think they got it right that way. Having different operations (functions, methods) for returning values or promises makes all this discussion go away. Of course we're a bit late to go in that direction, but it's interesting to keep it in mind. |
I agree with @ForbesLindesay's suggestion that we simply don't specify @domenic and @ForbesLindesay I'm not clear on why the boat has sailed for One data point is that Another data point, although not directly related to The primary problem with I am definitely sensitive to the fact that we have to be careful not to break the world. However, if there's little or no evidence that we would, then it feels like we should look at "reject" in a more forward-looking light. Breaking the identity function and creating hazards where simply returning an input swallows a rejection are both pretty serious downsides in my mind, and I don't have a clear picture of what the upsides of |
@juandopazo Yeah, that's a good point. Unfortunately, I think you're right that we can't pursue it without breaking the world, since |
I thought I'd been clear, but let me try again:
Anything that breaks the direct connection between the states fulfilled and rejected, and the verbs fulfill and reject, is not acceptable. Note that the above arguments intentionally tie the existence and survival of Thus, the only solution I see if we truly want your semantics, @briancavalier, is a new verb ( One idea I hesitate to even pose: rename "rejected" to "broken" in the base Promises/A+ spec, and use "reject" as the name for |
Just realized something: @briancavalier, in essence your point seems to be that promises-for-promises are hazardous, which I sort-of agree with. My question: does having only I feel like this was a lightbulb moment, guys :). |
The more we talk about this, the better I like |
Yes, as far as I'm aware you'd never be able to create a promise for a promise if we did that. You could of course crete a promise for an array containing a promise ( |
P.S. I'd be inclined to pay the cost of making This is under the assumption that almost nobody is currently attempting to reject with a promise. |
@domenic and @ForbesLindesay Yeah, I think along with polymorphic |
Wait a minute. If they throw in the factory function, we can just let that happen. Since the factory function is called synchronously, it'll crash the program. We don't have to transform it into reject at all. |
no, but it's a real pain if we don't. As you mentioned with not wanting Consider this function: function doAsyncWork() {
return new Promise(function (resolver) {
var resA = doSynchronousPreparationWhichMightThrowAnException();
resolver.resolve(doAsyncWork(resA));
});
} If you want to call it and be sure of handling all errors you need code to handle both synchronous and asynchronous errors, this is really bad. |
Since this has a polymorphic reject, it really has no chance of being accepted, so closing. |
After thinking about all of this a bit more, I wrote down this strawman for what I feel is a minimum API for promise creation and resolution. First, a few notes:
new Promise(fn)
idea, but simplifiesfn
's signature to accepting a singleresolve
function.fulfill()
andreject()
in favor of a singleresolve()
. My current feeling is thatfulfill()
is not a good thing.onFulfilled
oronRejected
throws a promise object promises-spec#65.Let's discuss :)
Promise Creation and Resolution API
new Promise(resolutionFunction)
Creates a new, pending promise whose fate can be sealed by calling the
resolve
function with a value or another promise. Afterresolve(valueOrPromise)
is called,valueOrPromise
is not a promise,p
will be transitioned to the fulfilled state, and its fulfillment value will bevalueOrPromise
valueOrPromise
is a fulfilled promise,p
will be transitioned to the fulfilled state, and its fulfillment value will bevalueOrPromise
's fulfillment valuevalueOrPromise
is a rejected promise,p
will be transitioned to the rejected state, and its rejection reason will bevalueOrPromise
's rejection reasonvalueOrPromise
is a pending promise,valueOrPromise
is fulfilled,p
will be transitioned to the fulfilled state, and its fulfillment value will bevalueOrPromise
's fulfillment valuevalueOrPromise
is rejected,p
will be transitioned to the rejected state, and its rejection reason will bevalueOrPromise
's rejection reasonresolutionFunction
throws an exception (lete
be the thrown exception),e
is not a promise,p
will be transitioned to the rejected state, and its rejection reason will bee
.e
is a fulfilled promise,p
will be transitioned to the rejected state, and its rejection reason will bee
's fulfillment valuee
is a rejected promise,p
will be transitioned to the rejected state, and its rejection reason will bee
's rejection reasone
is a pending promise,e
is fulfilled,p
will be transitioned to the rejected state, and its rejection reason will bee
's fulfillment valuee
is rejected,p
will be transitioned to the rejected state, and its rejection reason will bee
's rejection reasonPromise.reject(valueOrPromise)
Creates a new, eventually rejected promise.
valueOrPromise
is not a promise,p
's rejection reason will bevalueOrPromise
valueOrPromise
is a fulfilled promise,p
's rejection reason will be thevalueOrPromise
's fulfillment value.valueOrPromise
is a rejected promise,p
's rejection reason will be thevalueOrPromise
's rejection reason.valueOrPromise
is a pending promise,valueOrPromise
is fulfilled,p
will be transitioned to the rejected state, and its rejection reason will bevalueOrPromise
's fulfillment valuevalueOrPromise
is rejected,p
will be transitioned to the rejected state, and its rejection reason will bevalueOrPromise
's rejection reasonThe text was updated successfully, but these errors were encountered: