Closed domenic closed 8 years ago
Was any
basically something like?
const { token, cancel } = Token.any([one, two, three]); // token cancels if any `one` `two` `three` or itself are cancelled
If so, I believe I can share examples.
Just token =
instead of { token, cancel } =
, but yeah.
An example I used in my draft is your last library with cancellation. With CancelToken.race
, it would look like this:
function last(operation) {
var s = CancelToken.source();
return function(...args) {
s.cancel("next operation already started"); // cancel previous token
const token = args.pop();
if (token.requested) return Promise.reject(Cancel.getReason(token)); // this is weird
s = CancelToken.source(); // reinitialise source for next call
const any = CancelToken.race([token, s.token]); // wait for current token or next call
return operation(...args, any); // pass token into operation
};
}
I've never needed this in all our C# code, sorry. I did register the cancellation of one token to several tokens a few times - but being able to pass a CancellationTokenSource.Cancel
to Register
of another token has generally been sufficient.
Thanks @bergus; I added a version of that. I'd like to get a couple more examples before closing this.
Oh, and of course the trivial example can be a race
(which cannot do autocancellation for the loosers by itself, when using with tokens like in this proposal):
function example(token) {
const win = CancelToken.source();
const combined = CancelToken.any([token, win.token]);
return Promise.race([doA(combined), doB(combined)])
.finally(() => win.cancel("already settled"));
}
I'm not sure race motivates it @bergus :
function example(token) {
const win = CancelToken.source();
token.promise.then(t => win.cancel(t));
return Promise.race([doA(win), doB(win)])
.finally(() => win.cancel("already settled"));
}
@benjamingr Right, what is currently specced isn't really motivated at all as not being easily achievable without a builtin. It might be a different story if the .requested
property would immediately reflect the cancellation decision, see #45.
I would favour userland implementations over limited builtin ones, at least until we see which ones are really needed. The cancellation mechanisms should respect a custom .requested
property for that however.
And also, all of the above examples have only combined two tokens not arbitrarily many, so it might be a better idea to use tokenA.or(tokenB)
or tokenA.concat(tokenB)
instead of any([tokenA, tokenB])
.
I don't see the benefit of hamstringing it to only being useful for two items, when N allows for 2 just fine.
Well, I'd just leave figuring out the most useful functionality to userland implementations. And even if there was only a binary function, you always could do any = tokens => tokens.reduce(or)
:-)
@domenic I believe one of the examples we discussed was more or less as follows. I left it very verbose (minimized on abstractions) so I don't distract others, but could expand on more concise API that might make this nice?
The general concept is:
Tokens at play in this example are::
isRendered
isAnimating
The relationship between these tokens is:
isAnimating depends on isRendered, but isRendered does not depend on isAnimating.
class UIComponent {
constructor() {
// token to represent the the upper bounds on the UIComponent being alive.
this.isRendered = new Token(cancel => this._wasRemoved = cancel);
}
// triggered via by a UI event, or on initial render.
startAnimation() {
this.stopAnimation();
// create new token for said animation
let animationToken = new Token(cancel => this._stopAnimation = cancel);
// animate until:
// * the animation is cancelled
// * the UI component is removed
this.animate(Token.any([
animationToken,
this.isRendered
])
}
// triggered via by a UI event
stopAnimation() {
if (this._stopAnimation) { this._stopAnimation(); }
}
// UI Element was added to the UI
didInsert() {
// automatically start animation when the UI Component is inserted
this.startAnimation();
}
// UI Element was removed from the UI
willRemove() {
this._wasRemoved();
}
// animation loop
animate(until) {
if (until.isCancelled) { return; }
// some amazing animation:
requestAnimationFrame(this.animate.bind(this, until));
}
}
note: I have many more examples, and could extract use-cases from real world code if anyone believes that is of value
This example reminds me why I believe let { token, cancel } = Token.any([a,b,])
may be valuable.
In the above example (and many more I can think of) the following:
let animationToken = new Token(cancel => this._stopAnimation = cancel);
// animate until:
// * the animation is cancelled
// * the UI component is removed
this.animate(Token.any([
animationToken,
this.isRendered
]);
can be more succinctly represented as:
let animationToken = new Token(cancel => this._stopAnimation = cancel);
let { token, cancel } = Token.any([this.isRendered, ...otherDependentTokens])
this._stopAnimation = cancel;
this.animate(token);
Another spin, or more of a mutation of @bergus example could result in something like:
let { token, cancel } = this.isRendered.or(...otherDependentTokens)
this._stopAnimation = cancel;
this.animate(token);
I forgot what the use cases for it were :(. @dtribble @benjamingr could you help with some C#-derived use cases? @stefanpenner do you remember what the use case was that we spontaneously came up with at TC39?