NAME
Async::Hooks - Hook system with asynchronous capabilities
VERSION
version 0.15
SYNOPSIS
use Async::Hooks;
my $nc = Async::Hooks->new;
# Hook a callback on 'my_hook_name' chain
$nc->hook('my_hook_name', sub {
my ($ctl, $args) = @_;
my $url = $args->[0];
# Async HTTP get, calls sub when it finishes
http_get($url, sub {
my ($data) = @_;
return $ctl->done unless defined $data;
# You can use unused places in $args as a stash
$args->[1] = $data;
$ctl->next;
});
});
$nc->hook('my_hook_name', sub {
my ($ctl, $args) = @_;
# example transformation
$args->[1] =~ s/(</?)(\w+)/"$1".uc($2)/ge;
$ctl->next;
});
# call hook with arguments
$nc->call('my_hook_name', ['http://search.cpan.org/']);
# call hook with arguments and cleanup
$nc->call('my_hook_name', ['http://search.cpan.org/'], sub {
my ($ctl, $args, $is_done) = @_;
if (defined $args->[1]) {
print "Success!\n"
}
else {
print "Oops, could not retrieve URL $args->[0]\n";
}
});
DESCRIPTION
This module allows you to create hooks on your own modules that other developers can use to extend your functionality, or just react to important state modifications.
There are other modules that provide the same functionality (see "SEE ALSO" section). The biggest diference is that you can pause processing of the chain of callbacks at any point and start a asynchronous network request, and resume processing when that request completes.
Developers are not expect to subclass from Async::Hooks
. The recomended usage is to stick a Async::Hooks
instance in a slot or as a singleton for your whole app, and then delegate some methods to it.
For example, using Moose you can just:
has 'hooks' => (
isa => 'Async::Hooks',
is => 'ro',
default => sub { Async::Hooks->new },
lazy => 1,
handles => [qw( hook call )],
);
There are two main usages for hooks: notification or delegation of responsability.
You can define hook points for notification of important events inside your class. For example, if you where writting a feed aggregator, you could define a hook for notification of new items.
In some other cases, your module wants to make part of its bussiness logic extendable or even replaceable. For example, a SMTP server can ask if a specific mail address is a valid RCPT. All the registered callbacks would be called and if one of them has a definitive answer she can just stop the chain. You can even define a default callback to be called at the end, as a cleanup step.
You don't need to pre-declare or create a hook. Clients of your module should consult your documentation to discover which hooks to you support and then they should just call the hook()
method. It takes two parameters: a scalar, the hook name, and a coderef, the callback.
To call the hook chain, you use the call()
method. It requires a scalar, the hook to call, as the first parameter. The second optional parameter is an arrayref with arguments, or undef. The third optional argument, a coderef, is a cleanup callback. This callback will be called at the end of the chain or as soon as some callback ends the chain.
The callbacks all have a common signature. They receive two parameters. The first one is a Async::Hooks::Ctl object, used to control the chain of callbacks. The second is an arrayref with the arguments you used when the hook was called. Something like this:
sub my_callback {
my ($ctl, $args) = @_;
....
}
A third parameter is passed to the cleanup callback: a $is_done
flag, with a true value if the chain was ended prematurely done()
or stop()
.
The callback only has one responsability: decide if you want to decline processing of this event, or stop processing if we are done with it. Cleanup callbacks MUST just return.
To do that, callbacks must call one of two methods: $ctl->decline()
or $ctl->done()
. You can also use next()
or declined()
as alias to decline()
, and stop()
as alias to done()
, whatever feels better.
But you can delay that decision. You can start a network request, asynchronously, and only decide to decline or stop when the response arrives. For example, if you use the AnyEvent::HTTP module to make a HTTP request, you could do something like this:
sub check_server_is_up_cb {
my ($ctl, $args) = @_;
my ($url) = @$args;
http_get($url, sub {
my ($data, $headers) = @_;
if (defined $data) {
push @$args = $data;
return $ctl->done;
}
return $ctl->next;
});
}
In this example, we start a HTTP GET, and use a second callback to process the result. If a sucessful result is found, we stop the chain.
While the HTTP request is being made, your application can keep on processing other tasks.
METHODS
- $registry = Async::Hooks->new()
-
Creates a Async::Hooks object that acts as a registry for hooks.
You can have several object at the same time, independent of each other.
- $registry->hook($hook_name, \&cb);
-
Register a callback with a specific hook.
The callback will be called with two parameters: a Async::Hooks::Ctl object and an arrayref with arguments.
- $registry->call($hook_name [, \@args] [, \&cleanup])
-
Calls a specific hook name chain. You can optionally provide an arrayref with arguments that each callback will receive.
The optional cleanup callback will be called at the end of the chain, or when a callback calls
$ctl->done()
. - $count = $registry->has_hooks_for($hook);
-
Returns the number of callbacks registered with
$hook
.
SEE ALSO
There are a couple of modules that do similar things to this one:
Of those four, only Object::Event version 1.0 and later provides the same ability to pause a chain, do some asynchrounous work and resume chain processing later.
ACKNOWLEDGEMENTS
The code was inspired by the run_hook_chain
and hook_chain_fast
code of the DJabberd project (see the DJabberd::VHost module source code). Hat tip to Brad Fitzpatrick.
AUTHOR
Pedro Melo <melo@cpan.org>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2011 by Pedro Melo.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)