-
Notifications
You must be signed in to change notification settings - Fork 5
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
A simpler idea? #6
Comments
That's nice, I like the idea of just explicitly saying Case 1: var prom = get('https://foo.com/1')
.then((res) => get('http://foo.com/' + res));
...
prom.cancel(); Is there any circumstance in which the second promise gets properly cancelled? Case 2: I think uncancellable() {
return new CancellablePromise(
resolve => resolve(this),
() => {}
);
} That is to say we should still return a promise that can be rejected with an OperationCancelled exception by calling |
It looks good so far. I'm not thrilled about calling |
Your "then" implicitly supports parent cancellation instead of just cancelling the work you added by calling "then".
|
@skaegi Implicitly supporting parent cancellation is the entire goal: otherwise |
That's an interesting one we should probably make possible and think more about. Good catch.
The point of |
If that's what you want then write:
Alternately.... |
sigh
|
@domenic I see your point re case 2 @skaegi you've just demonstrated the problem. If it's anywhere near that difficult to propagate cancellation people won't bother to do so unless they personally need cancellation in their app, which would mean cancellation was never truely available to consumers of third party libraries. It needs to propagate by default, but propagation needs to be trivial to stop. |
Here's a simpler and probably better variation that doesn't override cancel
@ForbesLindesay Our app is very much in the space that needs cancellation and is something we both use and test every day. That absolutely means that we're willing to write code like above when we're writing a "filter" however far and away the more typical case is where we've add a new operation to the chain. Whatever the propagation behavior I want to make sure it's easy to cancel just that operation without impacting the parent and the rest of it's children. Our Deferred implementation used parent propagation for the last six months and we ultimately removed it and are much happier for it. Now when an operation is not cancelling correctly we can actually track it down instead of trying to understand why seemingly unrelated sibling calls are being rejected. With that said I'm not militantly opposed to the idea and will certainly follow whatever it is we standardize here. It would however really help if I could see a code snippet that showed how to localize the cancel propagation to just the operation |
So your desired semantics are something like this? get("http://example.com").then(function () {
return get("http://example.org");
}).cancel(); // should only cancel the get to example.org, not example.com ? |
Yes that is correct. Let me try to cover the cases as I see them...
For your example above where the
For your example above this is the case where the onFulfilled handler returns the promise from
|
If we replace then(cb, eb, ...args) {
var parents = [];
function wrap(fn) {
return function (...args) {
var res = fn(...args);
if (isPromise(res) && typeof res.cancel === 'function') {
parents.push(res);
}
return res;
}
}
return new CancellablePromise(
resolve => resolve(super(wrap(cb), wrap(eb), ...args)),
() => {
for (var parent of parents) parent.cancel();
this.cancel();
}
);
} The behavior is then: get("http://example.com")
.then(function () {
return get("http://example.org");
})
.cancel(); // should ancel the get to example.com or example.org
// (depending on which request is currently in progress) To support @skaegi's request: Add a method fork() {
return new CancellablePromise(resolve => resolve(this), noop);
} Which can be used: get("http://example.com")
.fork()
.then(function () {
return get("http://example.org");
})
.cancel(); // should only cancel the get to example.org, not example.com |
And equally: get("http://example.com")
.then(function () {
return get("http://example.org");
})
.fork()
.cancel(); // shouldn't cancel any requests, but still results in a rejected promise with the
// cancellation exception and get("http://example.com")
.then(function () {
return get("http://example.org");
})
.uncancellable()
.cancel(); // Truly doesn't cancel anything, does `cancel` even exist here? |
To make explanations clearer, I've separated out the promises so each one that gets created has its own name. var A = get("http://example.com");
var B;
var C = A.then(function () {
B = get("http://example.org");
return B;
});
//some time later...
C.cancel(); Case 2 This seems fine:
Case 3 This also seems fine:
Case 1 All promises are pending. C has not assumed any state. Without propagation:
With propagation:
Your proposed flow
alternatively
|
With the use of the fork method we get the behavior @skaegi desires (but with an explicitly created promise to not resolve, rather than having to magic one out of thin air): var A = get("http://example.com");
var B = A.fork();
var C;
var D = B.then(function () {
C = get("http://example.org");
return C;
});
//some time later...
D.cancel(); case 1
case 2 This seems fine:
case 3
|
Thanks @ForbesLindesay that all makes sense to me and covers the cases I care about. I personally think both |
Excellent, I think we're definitely moving towards agreement here. The use cases that may necessitate both function asyncMethodA() {
return somethingIDontWantCancelled()
.fork()
.then(somethingThatCanBeCancelledSafely);
}
var A = asyncMethodA();
// Some time later
A.cancel();//should only cancel second part of A
function asyncMethodB() {
return somethingThatINeedToShare()
.uncancellable();
}
var B = asyncMethodB();
//some time later
thirdPartyCodeIShouldBeSuspiciousOf(B);
//some time later
B.then(function (res) {
//do something important
//better hope third party code
//didn't cancel B
}); It would be good to flesh out what we think would be best for these two cases before the next strawman spec. P.S. I really like @domenic's idea of doing the strawman as just some source code with the appropriate behavior, it's a lot easier to see what's going on sometimes. |
Would inlining work? It would require directly creating new promises but I was hoping to minimize API on promise
|
Here's a first crack at a draft C |
That has nothing in it about propagation |
re: That has nothing in it about propagation Is the intent there to have a cancel call propagate to the children if the source has already been fulfilled? |
Yes, if |
If What happens when a new child is I buy that we might want this aggressive cancel behavior in some cases and you've shown how one might implement it however this feels to me like one of a range of implementation choices for a cancellable promise. This might not be popular however I feel the best we can do is say that |
I'm pretty sure the idea was that cancellation is just a type of rejection, and that if you're already fulfilled or rejected, cancellation should have absolutely zero impact. |
Ah, I see, the original post doesn't reflect that--- |
@skaegi I think you're missing something here and might want to re-read it a bit more carefully. var source = new Promise();
var outputA = source.then(function () { return childA; });
var outputB = source.then(function () { return childB; }); If If If If |
Hmm... that sounds exactly right to me (nearly Cancellation axioms) so I think I must be getting confused by the sample code and its use of Below is the (translated) code I have in my implementation. Does this do the same thing or is there a case where there are multiple children in that
Regardless, apologies as I think there is no problem here and we're in agreement on a reasonable default propagation mechanism. The other issue I wanted to cover was Is the intent there to give the |
Looks equivalent to me. I think you're correct that |
For specification purposes what if we didn't have
A promise implementation "could" of course add an 'onCancel' to their constructor however this would be a convenience and just sugar for:
|
The point of |
Here's an example of what I'm thinking
I'm not saying an implementation wouldn't provide I'm beginning to write tests now and noticed for external behavior I could replace the need for |
Interesting... nice illustration. |
Here's a gist to my Cancel tests -- https://gist.github.com/skaegi/4736504 . I just put the contents in CancelTests.js next to https://github.com/promises-aplus/promises-tests/tree/master/lib/tests and ran. One thing I found that is relevant to the spec is that the
... otherwise For the minimal implementation that means that instead of:
one should use...
|
I've updated my tests (https://gist.github.com/skaegi/4736504) to include an additional case where there was more than one fulfilled and rejected link in the promise chain before getting to a pending promise. For example:
This helped show a problem in the
|
For whatever it is worth, this is how I currently abort() a promise.
|
And timeout triggers abort().
|
Inspired by #1 (comment), what about something as simple as this? Assume underscored properties are "actually private," i.e. wouldn't be specced and are only here to illustrate. Using ES6 for brevity.
The text was updated successfully, but these errors were encountered: