Closed wayan closed 11 years ago
First of all, thanks for the patch, please do not let my disagreements with some of these changes discourage you, I am open to further discussion on all points.
That said, I am a little worried this does change too much.
For example I have a pipeline of objects (like Message::Passing, they are connected via output_to attribute). Each of the objects but the first one has a method consume which accepts message and returns promise.
There is a producer (STOMP subscription) which - when message appears in a queue - calls asynchronously:
$this->output_to->consume($message)->then(sub {
# if message was processed, remove it from the queue
$this->ack($message);
}, sub {
# what to do ?
});
There is an object (error handler) further on the pipe which lets the message go through and only if the processing fails stores the message for reinspection and returning to the pipeline.
sub consume {
my ($this, $message) = @_;
return $this->output_to->consume($message)->then(undef, sub {
my ($this, $error) = @_;
$this->store_error_message($message, $error);
return;
});
}
By this we make the promise for producer eventually fulfilled and the message is acknowledged.
my $q1 = $p->then(sub {});
my $q2 = $p->then(sub {}, sub {});
my $q3 = $p->then(sub {}, sub { die $_[0] });
$q1 and $q3 are rejected with the same reason as $p, but $q2 is fulfilled with empty result []. Which, now I see, implies that you cannot reject a promise without $reason, which is logical.
Lets organize these so we can keep track better.
Okay, so basically the only change for this is to wrap this line (https://github.com/stevan/promises-perl/blob/master/lib/Promises/Deferred.pm#L102) in a try/catch block to catch any exceptions which might be thrown, and then to call reject
with the exception as the argument if one is thrown. Is that correct?
not at all. You also need to change the line 110 from
$d->$method( @results )
to
$d->resolve( @results )
I don't understand that change, it would cause this line to not work correctly.
https://github.com/stevan/promises-perl/blob/master/lib/Promises/Deferred.pm#L86
Oh wait, I see now, I wasn't reading the spec correctly. I think that is just wrong behavior, and it would seriously break backwards compatibility.
I believe that this part of the spec is there because things like throwing exceptions is pretty much the only way people do errors in Javascript, in Perl this is not the case.
I think this beviour is good :-) because it allows you to escape from onRejected handler and thus nice error handlers.
sub handle_error {
my $p = shift();
$p->then(undef, sub {
my ($error) = @_;
die $error if isFatalForMe($error);
storeTheError($error);
return;
});
}
while with your semantics, you have to change "the chain" and create one more promise.
sub handle_error {
my $p = shift();
my $q = deferred();
$p->then(undef, sub {
my ($error) = @_;
if (isFatalForMe($error)){
$q->reject($error);
}
else {
storeTheError($error);
$q->resolve();
}
});
return $q->promise;
}
But yes, it would definitely break the backward compatibility of Promises.pm.
I think I need to keep the backwards compatibility, so I will have say no to this feature, but as a compromise I would accept a patch that allowed this behavior to be used based on a flag, something like.
Promises::Deferred->new( resolve_exceptions => 1 )
It would also mean having to have Promises::deferred pass through parameters, but that is a simple change. Would that work for you?
@wayan - just FYI, I need to make this change soon, so just be aware that the master branch will have changes in it.
"Would that work for you?" . Probably not. I understand that this is radical twist in behaviour. Better to keep the semantics of Promises.pm simple and unchanged and spare the arguments of defer for future use.
For myself I will probably make some very simple implementation in my private namespace or under different name on CPAN (AnyEvent::Promise?). I think to make it AnyEvent specific and thus complying to 2.2.4 (the handlers running outside of the current executon stack) of the spec also.
@wayan - another option would be to make _wrap
part of the public API so that you can easily subclass it and adjust the behavior accordingly. Additionally creating an extension point for your AnyEvent specific 2.2.4 behavior as well. Does that work?
@wayan - are you still interested in re-doing this patch? Or have you decided to write one yourself instead?
@stevan - sorry for delay. Finally I have decided to write my own simple implementation instead with the same interface.
Regarding _wrap it would probably work to have in subclass:
around wrap => sub {
my $orig = shift;
my $code = $orig->(@_);
sub {
AE::postpone($code);
};
};
but It would make the meaning of intermediate states less clear.
@wayan - no worries, I will implement all the bits we agreed upon anyway. Thanks for the initial work and the discussion.