Writing good object-oriented code under AnyEvent
Executive Summary: you either want inversion of control (threads with Coro that block) or a state machine.
You could use Coro, which can convert the infinite ladder into linear code (by inversion of control), e.g. using Coro::rouse_cb/rouse_wait, or some of the Coro::AnyEvent functions:
do_sth cb => sub { ...
becomes (if the callback is only called once):
do_sth cb => Coro::rouse_cb;
my @res = Coro::rouse_wait;
Your only other option is to use a state machine, e.g. using an object (there are many ways to implement state machines, especially in Perl):
my $state = new MyObject;
do_something callback => sub { $state->first_step_done };
And in first_step done, you register a callback for $self->next_state_done etc.
You can also look into some cpan modules, such as AnyEvent::Blackboard or AnyEvent::Tools - I haven't used them myself, but maybe they can help you.
As for condvars, I am personally not so hot about using them as the only means for an API - I prefer callbacks (as condvars are valid callbacks this allows both), but condvars do allow you to raise exceptions in the consumer (via the croak method).
Well, one thing I can think of is using slightly modified Chain of Responsibility pattern:
my $params = {
user => $user,
event => $event,
session => undef
};
my @chain = ('get_session', 'do_stuff', 'save_session', 'emit');
for my $index (0..$#chain) {
my $current = $chain[$index];
my $next = $chain[$index + 1] || undef;
$self->{$current}($params,
on_error => sub { $self->error($params) },
on_success => sub { $self->{$next}($params) },
);
}
It's a bit rough, but I hope it shows the point. )
You may want to encapsulate it in a Future object using the Future
module. That adds syntactical sugar to make this cleaner.