Open bergus opened 10 years ago
It's still a bit difficult to undersand how it works. Can you see if this the code below demonstrate the full scenario?
var defer = promise.defer(),
p1 = defer.promise
var p4 = p1.then(function(value) {
var p2 = new Promise();
return p2;
}, function(error) {
var p3 = new Promise();
return p3;
}, onProgress, cToken);
//cToken associated with p4
//cToken registered with p1
//cToken registered with p2
//cToken registered with p3
var p5 = p4.then(function(){});
//cToken registered with p5
//--- case 1: try cancel p1
p1.cancel(new CancellationError);
// only p1 is cancelled as there's no associated cancellation token
//--- case 2: try cancel p1, and cToken
p1.cancel(new CancellationError, cToken);
// p1, p2, p3, p4 and p5 is cancelled, as cToken is revoked
//--- case 3: try cancel p4 without token
p4.cancel(new CancellationError);
//only p4 is cancelled, as cToken is not revoked
//--- case 4: try cancel p4 with token
p4.cancel(new CancellationError, cToken);
// p1, p2, p3, p4 and p5 is cancelled, as cToken is revoked
// NOTE that p1 is also cancelled. as "If parentPromise is pending, attempt to cancel it with error."
Hi, thanks for your interest (after such a long time :-) - I had to re-read the draft myself). Unfortunately, most of your code does not behave as intended by me. The core idea behind this design with the cancellation tokens was that promises cannot get cancelled while there are still (uncancelled) handlers registered on it. Only by revoking all handlers (and their respective tokens) you can cancel a promise.
So let's step through your code:
var defer = promise.defer(),
p1 = defer.promise;
var p4 = p1.then(function(value) {
var p2 = new Promise();
return p2;
}, function(error) {
var p3 = new Promise();
return p3;
}, onProgress, cToken);
So far so good. Notice that p1
is pending, the success/error handlers are not executed.
I'm missing a declaration like var cToken = {cancelled: false};
//cToken associated with p4
//cToken registered with p1
Yes. You've passed an explicit token, which is associated with p4
and registered - together with the 3 handlers - on p1
.
//cToken registered with p2
//cToken registered with p3
No. Neither handler is executed yet, p2
and p3
don't even exist at this point. Once p1
would be fulfilled or rejected, one of them would - only then cToken
is registered with the child promise.
var p5 = p4.then(function(){});
//cToken registered with p5
No. As you haven't passed any token, a new implicit token is created that is associated with p5
and registered - together with that handler - on p4
.
p1.cancel(new CancellationError); //--- case 1: try cancel p1
// only p1 is cancelled as there's no associated cancellation token
Not necessarily. There is no associated token, yes, but that doesn't mean it's automatically cancelled. p1
is created by some deferred, and that will need to handle the cancellation attempt in some implementation-defined way. It would however know that the still uncancelled cToken
is registered on it, and probably not do anything.
p1.cancel(new CancellationError, cToken); //--- case 2: try cancel p1, and cToken
// p1, p2, p3, p4 and p5 is cancelled, as cToken is revoked
Yes, cToken
is revoked and following the above logic the deferred will most likely decide to reject p1
with the error. However, there were no attempts at cancelling p4
and p5
, they will stay mostly unaffected. As cToken
is cancelled, the success and error callbacks are cancelled as well, and neither of them will get called (p2
and p3
never are created). p4
stays forever pending (until being cancelled).
p4.cancel(new CancellationError); //--- case 3: try cancel p4 without token
//only p4 is cancelled, as cToken is not revoked
No. With cToken
not being revoked, p4
won't be cancelled.
p4.cancel(new CancellationError, cToken); //--- case 4: try cancel p4 with token
// p1, p2, p3, p4 and p5 is cancelled, as cToken is revoked
// NOTE that p1 is also cancelled. as "If parentPromise is pending, attempt to cancel it with error."
Yes, p1
and p4
are successfully cancelled. p2
and p3
never were created (p1
is still pending). p5
is "cancelled" in the sense that p4
was rejected with the cancellation error, and there was no attempt to catch this so p5
is rejected as well.
If these results seem queer, notice that the explicit cancellation token is only a tool for explicit control over the cancellation process (which might be supplied by the user). Most usages of this proposal will rely on the implicitly created cancellation tokens that will get automatically revoked when there are no more handlers.
Thanks so much for your time to walk me through. I'm a bit more understood there. With a few more questions :).
Register this cancellation token with the parentPromise.
Why does it need to register the cancellation token with the paren promise, p1
in the above scenario. what's a possible use case there? Won't it be more intuitive to only allow token associate with p1
during the creation of p1
?
p4.cancel(new CancellationError);
Why do we need to supply a CancellationError
during a cancel
call? Who will handle it, the onRejection
handler? As I can't see a onCancellation
handler to assign with.
p4.cancel(new CancellationError, cToken);
I think it would be more intuitive if cancelling a promise actually revoke the cancellationToken associated with it? i.e. without the second parameter. How do you think?
Why does it need to register the cancellation token with the parent promise
Because that is the most important purpose of the tokens: registering a token together with one or two handlers signals to the promise that someone is interested in its result. Cancelling this token later signals the disinterest - the handlers shall be removed and never be executed.
You never want to cancel a "promise" - a promise is nothing, it's a mere placeholder for a result. You want to cancel the task that is fetching the value, in the case of then
this is "waiting for the parent and executing the callbacks".
The use case are promises that are not linear chains, but have multiple handlers installed - see the ajax example.
Why do we need to supply a
CancellationError
during acancel
call?
To distinguish cancellations from other causes that prevented the promise from completing normally.
Who will handle it, the
onRejection
handler? As I can't see aonCancellation
handler to assign with.
Possibly, if step 4 is un-bracketed - I'm not sure about this.
The cancellation error becomes the rejection reason of the promise, so that if you cancelled a promise and later tried to add another handler on it, you'd see it rejected with this error.
Also, any promises not created by then
but by some other implementation-dependent mechanism are free to handle cancellation attempts in any way they like - e.g. calling an onCancellation
handler with the error.
I think it would be more intuitive if cancelling a promise actually revoke the cancellationToken associated with it?
Yes, that's the default behaviour for implicit cancellation tokens - you will never see them. The cToken
parameter is optional and would only be used with explicit tokens (though when you use explicit tokens and maintain reference to them anyway, you might cancel the tokens in some other ways as well).
Thanks for explanation, that help me clarified a lot of things! @bergus
The part I like the most is the extension to the promise/A+ then
specification. And the ability to cancel chained promises.
Initially, I feel that using the token would be easy to understand. However, after I tried the usage it seems that the down said is the code readability that's it's not intuitive to know which promise and it's pending task is being involved due to chained promise. Or how when to supply a token or not.
I have idea to simplify that other than token tho, I will share it in a separate thread as another proposal. Hope you don't mind.
I really love the idea of cancellation tokens from #8. However, I will go even further and amend/modify the specification for
then
, so that handlers themselves can be prevented from execution.Terminology
CancellationToken
is an object with methods for determining whether an operation can be cancelled.CancellationToken
might be associated with a promise.CancellationToken
s can be registered with a promise.CancellationError
is an error used to reject cancelled promises.CancellationError
.CancellationToken
that is in the cancelled state, denoting that the result of the operation is no longer of interest. It might be considered an unregistered token or a revoked token.onFulfilled
oronRejected
argument to a.then()
call whosecancellationToken
has been revoked.Requirements
The
then
methodExtensions are made to the following sections: 2.2.1. If
onFulfilled
is a function: 2.2.1.1. it must be called _unless it is cancelled_ afterpromise
is fulfilled, withpromise
’s value as its first argument. 2.2.2.1. IfonRejected
is a function, 2.2.2.2. it must be called _unless it is cancelled_ afterpromise
is rejected, withpromise
’s reason as its first argument.Note: 2.2.1.3. and 2.2.2.3. ("must not be called more than once") stay in place, and still at most one of the two is called.
2.2.6.1. If/when
promise
is fulfilled, all respective _uncancelled_onFulfilled
callbacks must execute in the order of their originating calls tothen
. 2.2.6.2. If/whenpromise
is rejected, all respective _uncancelled_onRejected
callbacks must execute in the order of their originating calls tothen
.2.2.7.3. If
onFulfilled
is not a function andpromise1
is fulfilled _andpromise2
was not cancelled_,promise2
must be fulfilled with the same value aspromise1
. 2.2.7.4. IfonRejected
is not a function andpromise1
is rejected _andpromise2
was not cancelled_,promise2
must be rejected with the same reason aspromise1
.(we probably need these last two in every cancellation spec anyway)
The
CancellationToken
A
CancellationToken
is an object with a unique identity. It can get revoked, moving it into the cancelled state, which is an irreversible change.The object has an
isCancelled
property, whose value must be a boolean[, or a function that returns a boolean]. It must yieldtrue
if the token is in the cancelled state, andfalse
otherwise.Retrieving the state of a cancellation token must not change the state, i.e. an
isCancelled
function must have no side effects.The
CancellationError
Error
(cancellationError instanceof Error === true
).name
property with value"CancellationError"
.cancelled
property with valuetrue
.The
cancellationToken
parameterThe fourth parameter of the
then
method is an optionalcancellationToken
; a call does look likeIf
cancellationToken
is not aCancellationToken
object, create an implicit CancellationToken for the newpromise
. In both cases (explicit and implicit) associate it with the newpromise
. The state of an explicit token must not be changed by thethen
method.Register this cancellation token with the
parentPromise
.Also register this cancellation token with any
child
promises that are returned fromonFulfilled
oronRejected
(2.2.7.1). This includes passing the cancellation token to thethen
call in step 2.3.3.3. of the Promise Resolution Procedure.If the
promise
is attempted to be cancelled with anerror
, run the following steps:parentPromise
is pending, attempt to cancel it witherror
.onRejected
is a function and neither it noronFulfilled
have been called, execute it with theerror
as its argument. (with 2.2.4. "async execution", and 2.2.7.1. "assimilation" in mind)]onFulfilled
oronRejected
have been called and returned achild
promise, attempt to cancel that witherror
.onRejected
will not be executed]], rejectpromise
witherror
.The
cancel
methodThe
cancel
method of a promise accepts two optional parameters:promise
is still pending. Returnfalse
otherwise.reason
is aCancellationError
, leterror
be that error object, else leterror
be a newCancellationError
with thereason
as the value of itsmessage
property.token
is aCancellationToken
, revoke it.promise
witherror
.The
Promise
constructorPromises not created by a call to
then
may handle attempts to cancel them in implementation-dependent ways.Constructors are however encouraged to signal these to the promise creators, and optionally provide them access to the list of registered tokens. This might be done through a callback that is passed as an additional argument to the
Promise
constructor, or returned from theresolver
call.Pluses:
no ambiguity when a promise is cancelled before the handlers of its resolved parent could be executed
.then(null, null, null, {isCancelled:false})
.then()
Minus:
finally
in terms of.then()
without preventing cancellation - it is not possible to add a handler via calling.then()
without registering a token.The basic idea of this draft is that handlers that were passed to
then
will not be executed when the cancellation token that accompanied them is cancelled:I'm not so sure about the steps 4 and 6 of cancellation attempts.
effectshandlers, notice that all forks/branches have beencutcancelled so it's a linear chain). For discussion, see issue #10.