Closed yahuio closed 3 years ago
I do like the idea of message passing to the producer of a promise as much as you, especially if we talk about promises that use "progress" notifications to emit messages by themselves.
However I think that cancellation should be something more specific. In your proposal, you call .cancel()
but can have no expectations on what the promise does with it - it might call either success or failure callbacks, or even just neither of them; right now or later or never. This is quite problematic from the consumer's point of view, and imo from a promise library implementer's as well - who needs to deal with memory leaks on pending promises.
Especially the resuming of suspended promises is problematic in that regard. You say
var p3 = p2.then(function() {
// never called as suspended by p3.cancel below
});
p3.cancel();
but what would happen if someone called p3.then()
now? Another thing that I don't understand is how your oncancel
handlers knows about the multiplicity of subscribed consumers. How could it decide to "cancel the task only if there are no more consumers"? What about consumers who are late for the partypromise?
I think it would be nice to have some kind of .send(message)
mechanism that can be used for things like suspension and resumption, or even for cancel messages (my own promise implementation does this in fact), but cancellation should have a more restricted meaning and clearly defined effects.
This is quite problematic from the consumer's point of view, and imo from a promise library implementer's as well - who needs to deal with memory leaks on pending promises.
Right. I agree. I don't have any clean solution on this. Perhaps it's too ideal.
Another thing that I don't understand is how your oncancel handlers knows about the multiplicity of subscribed consumers. How could it decide to "cancel the task only if there are no more consumers"? What about consumers who are late for the partypromise?
Yes, it's not something manageable, unless the Producer keep track a reference of the Consumer to see identify which specific Consumer had cancelled the request. But it would lead to a memory leak issue as you said.
We, team in my company had an interesting discussion on it. We mirror the concept to a fast food shop scenario. Where the customers would be the Consumer at the end of the promise chain, requesting an order at the counter (Producer of the last node in the chain). Then request the food production node chain to create the food. Upon cancel request form the customer (Consumer) at the counter, the counter propagate the cancellation message to each node, and stops their task if it's not demanded by other customers. So in the real world, normally an order ID would be given to identify the customer.
I imply this in the promise implementation. All promise would be given an ID. If a child promise is created from parent promise through then
. Then, when child promise get cancelled, the child promise will have to use it's ID to propagate the cancellation to the parent promise. parent promise then check if this ID is a valid chlid, if so, decrement a demand of it. And if it reaches 0, it will cancel it's task.
This isn't an ideal solution for sure that we know. In real use cases the IDs have to be managed properly to ensure cancellation will be carried. i.e. If an ID is lost, then the promise won't get cancelled as ref count will never decrement to 0.
Thoughts?
All promise would be given an ID.
All promises are objects, which already have an identity :-) Yeah, reference counting will probably be the way to go. This can be done in various ways without leaking memory, even if JS does not support weak references out of the box. The IDs you're talking of are a detail of one possible implementation.
Updated.
I've implemented cancellable promises in PHP that might be of interest to this group. The implementation can be found here: https://github.com/icicleio/Icicle/tree/master/src/Promise. There's no reason that cancellation could not be implemented similarly in JS.
In the above implementation, the parent promise is only cancelled if all children (consumers) of the promise have also been cancelled. This is done by keeping a simple count of children and decrementing the count if a child is cancelled. Only if the count is 0 when a child is cancelled is the parent promise also cancelled. Cancelling a promise is similar to rejecting the promise, so subsequent calls to then()
will call the rejection callback.
Closing, not following anymore.
Revisit Promise Concept
The Promise pattern is about Consumer and Producer. ConsumerA request ItemA from the ProducerA, ProducerA creates a Promise pA with TaskA (suppose to produce ItemA) to return to Consumer A immediately, regardless whether ItemA is available now or later. So Promise is the kind of the proxy to access A, providing to Producer utility of
then
. However, when P(ItemA)'sthen
is being triggered with handleronFulfilled
, P(ItemA) itself becomes a Producer to create a Promise for the new Consumer.So what's "cancel" at conceptual level?
Thanks to @bergus, led me to this understanding of "cancel": Consumer cancels it's the request (demand of ItemA) from the Producer. Which is only a message to the Producer, so that Producer can response to that, such as:
Therefore, Promise itself cannot be cancelled,
cancel
is just a specific message Consumer wants to tell the Producer. It's up to the Producer to decidewhat to do with itwhether to cancel the task or not.As stated in the concept section,
then
is a Producer that creates another Promise. So it's up tothen
to decide wether to cancel the task when it receives a cancel request.Cancel handling for the Producer
then
In my understanding, the task of
then
is, run the handler upon fulfilment/rejection.So, when it's being cancelled (while it's unresolved yet of coz'), it should cancel the task on them. However, since a Promise may have multiple Consumers request through multiple triggers of
then
, it should ONLY cancel the task if there're no more Consumers.Therefore, Promise should provides the ref counting utility for Producer to get to know how many Consumers are pending.
In Summary
then
suspendscancels it's task, run the handler upon fulfillment/rejection, upon cancellation message received, ONLY if a. Promise ofthen
is still PENDING b. there's no more ConsumerAPI Proposal
Constructor
Promise Instance
cancel
MethodCancel a promise with message and consumerId. e.g. The consumerId would be the id of the promise created from
then
.Promise Instance
isCancelled
MethodReturns true if promise is in Cancelled state
Promise Instance
getId
MethodReturns a unique id of a promise
Sample Usage
Cancel
then
produced Promise, Producerthen
cancel it's taskCancelling
then
produced Promise that has more than one ConsumerCancel
delay
produced Promise results in rejectionChange Log
16/3/15
then
method. Found it difficult to expect the sequence as it's may not be consistent to general fulfill/reject handler.15/3/15