Re: Cancellation architectural observations

Thanks for this summary of some concerns!  All valid, I think.

In the GitHub issue
<https://github.com/slightlyoff/ServiceWorker/issues/625>, there are
some additional usability concerns, which I think make the
cancellation token approach much less attractive, and lean the desired
solution more towards a promise subclass.  In particular:

Cancellations should "chain"
======================
If you have a cancellable promise p1, and use .then() to produce a new
promise p2, p2 should also be cancelable, and in the default case,
should "chain up" to p1 and cause it to cancel as well.

If you chain multiple promises off of p1, like p2a and p2b, then
canceling either one of the p2X promises should do nothing, but
cancelling *both* of them should cancel p1. In other words, p1 can
ref-count its "child" promises that retain cancellation abilities, and
cancel itself when everything consuming its result has been cancelled.

This is important so you don't have to explicitly keep track of every
single cancelable thing you're doing, if you're only using it to
immediately chain onward again.  You can just care about the final
result, and if you end up not needing it, you can cancel it and it
walks back and tries to cancel everything your result depends on.

Combinators should combine cancellations
=================================

If you do `let fp3 = FetchPromise.all(fp1, fp2)`, then an fp3.cancel()
should try to cancel fp1 and fp2, as noted above.  You want all the
(cancellation-aware) combinators to be just as friendly as chaining
directly, for usability.

You need to be able to "clean" a cancellable promise
========================================

If the promise is what carries the cancellation ability, you need to
be able to observe its value without carrying the cancellability
around, to prevent spreading power around in an unwanted way (and
prevent upping the "chained promises" refcount).  This is doable by
just wrapping it in a standard promise - `Promise.resolve(fetch(...))`
will return a normal non-cancellable promise.


A cancellation token is basically an ocap, and that means you have to
keep track of the ocaps explicitly and separately from the promise for
the result.  This means more value-passing, and when you return
another cancellable promise in the callback (like
`fetch(...).then(x=>fetch(...))`), you have to explicitly smuggle that
cancellation token out of the callback and hold onto both of them.
Combinators become annoying, as you have to grab *all* of the cancel
tokens used and hold them together, etc.

Attaching cancellation to the promise just provides more usable
behavior overall, without preventing safe behavior when you desire it.

~TJ

Received on Monday, 2 March 2015 23:07:45 UTC