File Coverage

blib/lib/Async/Chain.pm
Criterion Covered Total %
statement 55 58 94.8
branch 10 16 62.5
condition 3 6 50.0
subroutine 12 12 100.0
pod 5 5 100.0
total 85 97 87.6


line stmt bran cond sub pod time code
1             package Async::Chain;
2              
3 2     2   34660 use 5.006;
  2         9  
  2         124  
4 2     2   12 use warnings FATAL => 'all';
  2         4  
  2         134  
5 2     2   5998 use overload ('&{}' => \&_to_code, fallback => 1);
  2         2625  
  2         19  
6 2     2   155 use Carp;
  2         4  
  2         1327  
7              
8             =head1 NAME
9              
10             Async::Chain - The right way to convert nested callback in plain struct
11             or just the syntax sugar for guy who do not like deep indent.
12              
13             =head1 VERSION
14              
15             Version 0.05
16              
17             =cut
18              
19             our $VERSION = '0.05';
20              
21             =head1 SYNOPSIS
22              
23             Every subroutine in the chain receive callable object as first argument followed
24             by arguments of object call. You can break chain in every sub, just do not call
25             C<$next>.
26              
27             You can skip some subroutins using C or C method.
28              
29             use Async::Chain;
30              
31             # with chain call
32              
33             chain
34             sub {
35             my next = shift;
36             AnyEvent::HTTP::http_get('http://perldoc.perl.org/', $next);
37             },
38             sub {
39             my next = shift;
40             return $next->jump('log')->(0, "not a 200 response");
41             ...
42             $db->async_insert(..., cb => $next);
43             },
44             sub {
45             my next = shift;
46             ...
47             $next->($status, $message);
48             },
49             log => sub {
50             my next = shift;
51             my ($status, $message) = @_;
52             ...
53             log(...);
54             };
55              
56             =head1 RATIONALE
57              
58             A asynchronous code often have deep nested callbacks, therefore it is tangled
59             and hard to change. This module help to converta a code like following to some
60             more readable form. Also, with C you can easily skip some unneeded steps
61             in this thread. For example jump to log step after the first failed query in
62             the chain.
63              
64             without chain:
65              
66             sub f {
67             ...
68             some_anync_call @args, cb => sub {
69             ...
70             some_other_anync_call @args, cb => sub {
71             ...
72             ...
73             ...
74             yet_another_anync_call @args, cb => sub {
75             ...
76             }
77             }
78             }
79             }
80              
81             using chain:
82              
83             chain
84             sub {
85             my next = shift;
86             ...
87             some_anync_call @args, cb => sub { $next->(@arg) }
88             },
89             sub {
90             my next = shift;
91             ...
92             some_other_anync_call @args, cb => sub { $next->(@arg) }
93             },
94             sub {
95             my next = shift;
96             ...
97             },
98             ...
99             sub {
100             ...
101             yet_another_anync_call @args, cb => sub { $next->(@arg) }
102             },
103             sub {
104             ...
105             };
106              
107             If you don't need to skip or hitch links, you can use 'kseq' function from CPS
108             module, that slightly faster.
109              
110             =head1 SUBROUTINES/METHODS
111              
112             =cut
113              
114             # Internal method called by use function
115             sub import {
116 2     2   18 $caller = (caller())[0];
117 2         4 *{$caller . "::chain"} = \&chain;
  2         43  
118             }
119              
120             # Internal method used for reduction to code.
121             sub _to_code {
122 7     7   20 my $self = shift;
123             return sub {
124 7         18 my $cb = shift @{$self} or
125 7 50   7   8 return sub { };
  0         0  
126 7         21 $cb->[1]->($self, @_);
127 7         1198 ();
128             }
129 7         46 }
130              
131             =head2 new
132              
133             The Asyn::Chain object constructor. Arguments are list of subroutine optionaly
134             leaded by mark.
135              
136             =cut
137              
138             sub new {
139 1 50   1 1 2 my $class = shift; $class = ref $class ? ref $class : $class;
  1         4  
140 1         2 my $self = [ ];
141             # FIXME: check args type
142 1         4 while (scalar @_) {
143 12 100       19 if (ref $_[0]) {
144 6         15 push @$self, [ '', shift ];
145             } else {
146 6         20 push @$self, [ shift, shift ];
147             }
148             }
149 1         5 bless $self, $class;
150             }
151              
152             =head2 chain
153              
154             Only one exported subroutine. Create and call Anync::Chain object. Return empty
155             list.
156              
157             =cut
158              
159             sub chain(@) {
160 1     1 1 42 my $self = __PACKAGE__->new(@_);
161 1         38 $self->();
162 1         119 ();
163             }
164              
165             =head2 skip
166              
167             Skip one or more subroutine. Skipe one if no argument given. Return
168             Anync::Chain object.
169              
170             =cut
171              
172             sub skip {
173 1     1 1 6 my ($self, $skip) = @_;
174 1 50 33     8 $skip = ($skip and $skip > 0) ? $skip : 1;
175 1         4 while($skip) {
176 1         1 shift @{$self}; --$skip;
  1         8  
  1         4  
177             }
178 1         10 $self;
179             }
180              
181             =head2 jump
182              
183             Skip subroutines for first entry of named mark. Return Anync::Chain object.
184              
185             =cut
186              
187             sub jump {
188 1     1 1 7 my ($self, $mark) = @_;
189 1   66     1 while(scalar @{$self} and ${self}->[0]->[0] ne $mark) {
  3         19  
190 2         1 shift @{$self};
  2         4  
191             }
192 1         3 $self;
193             };
194              
195             =head2 hitch
196              
197             Move named link to beginning of the chain. When link with given name not exists
198             or first in chain, method has no effect. Return Anync::Chain object.
199              
200             =cut
201              
202             sub hitch {
203 1     1 1 7 my ($self, $mark) = @_;
204 1         2 my ($index, $link) = (0, undef);
205              
206 1 50       3 unless ($mark) {
207 0         0 croak "hitch called with empty mark";
208 0         0 return $self;
209             }
210              
211 1         3 for (@$self) {
212 4 100       16 if ($_->[0] eq $mark) {
213 1 50       5 $link = splice (@$self, $index, 1) if ($index);
214 1         2 last;
215             }
216 3         4 $index++;
217             }
218              
219 1 50       4 unshift (@$self, $link) if ($link);
220 1         4 $self;
221             }
222              
223             =head1 AUTHOR
224              
225             Anton Reznikov, C<< >>
226              
227             =head1 BUGS
228              
229             Please report any bugs or feature requests, or through GitHub web interface at
230             L.
231              
232             =head1 SUPPORT
233              
234             You can find documentation for this module with the perldoc command.
235              
236             perldoc Async::Chain
237              
238             =head1 ACKNOWLEDGEMENTS
239              
240             Mons Anderson - The original idia of chain and it first implementation.
241              
242             =head1 LICENSE AND COPYRIGHT
243              
244             Copyright 2014 Anton Reznikov.
245              
246             This program is free software; you can redistribute it and/or modify
247             it under the terms of the GNU General Public License as published by
248             the Free Software Foundation; version 2 dated June, 1991 or at your option
249             any later version.
250              
251             This program is distributed in the hope that it will be useful,
252             but WITHOUT ANY WARRANTY; without even the implied warranty of
253             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
254             GNU General Public License for more details.
255              
256             A copy of the GNU General Public License is available in the source tree;
257             if not, write to the Free Software Foundation, Inc.,
258             51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
259              
260              
261             =cut
262              
263             1; # End of Async::Chain