stevan / promises-perl

An implementation of Promises in Perl
31 stars 29 forks source link

makes the exception handling closer to Promises/A+ specification #3

Closed wayan closed 11 years ago

wayan commented 11 years ago
stevan commented 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.

wayan commented 11 years ago

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.

stevan commented 11 years ago

Lets organize these so we can keep track better.

Agreed

Not Agreed

wayan commented 11 years ago
stevan commented 11 years ago

Not Agreed

Agreed

wayan commented 11 years ago
stevan commented 11 years ago

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?

wayan commented 11 years ago

not at all. You also need to change the line 110 from

$d->$method( @results )

to

$d->resolve( @results )
stevan commented 11 years ago

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

stevan commented 11 years ago

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.

stevan commented 11 years ago

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.

wayan commented 11 years ago

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.

stevan commented 11 years ago

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?

stevan commented 11 years ago

@wayan - just FYI, I need to make this change soon, so just be aware that the master branch will have changes in it.

wayan commented 11 years ago

"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.

stevan commented 11 years ago

@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?

stevan commented 11 years ago

@wayan - are you still interested in re-doing this patch? Or have you decided to write one yourself instead?

wayan commented 11 years ago

@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.

stevan commented 11 years ago

@wayan - no worries, I will implement all the bits we agreed upon anyway. Thanks for the initial work and the discussion.