File Coverage

blib/lib/Async/Methods.pm
Criterion Covered Total %
statement 12 72 16.6
branch 0 24 0.0
condition 0 5 0.0
subroutine 4 20 20.0
pod 0 1 0.0
total 16 122 13.1


line stmt bran cond sub pod time code
1             package Async::Methods;
2              
3             our $VERSION = '0.000004'; # v0.0.4
4              
5             $VERSION = eval $VERSION;
6              
7 2     2   1218 use strict;
  2         10  
  2         66  
8 2     2   10 use warnings;
  2         4  
  2         59  
9 2     2   9 use Carp ();
  2         4  
  2         35  
10 2     2   1240 use Hash::Util qw(fieldhash);
  2         6512  
  2         15  
11              
12             fieldhash my %start;
13             fieldhash my %then;
14             fieldhash my %else;
15              
16             package start;
17              
18             sub start::_ {
19 0     0     my ($self, $method, @args) = @_;
20 0           my $f = $self->$method(@args);
21 0           $start{$f} = $self;
22 0           return $f;
23             }
24              
25             sub AUTOLOAD {
26 0     0     my ($self, @args) = @_;
27 0           my ($method) = our $AUTOLOAD =~ /^start::(.+)$/;
28 0           $self->start::_($method => @args);
29             }
30              
31             package then;
32              
33             sub then::_ {
34 0     0     my ($self, $method, @args) = @_;
35 0           my $f_type = ref($self);
36 0           my $f; $f = $self->then(
37 0     0     sub { my $obj = shift; $obj->$method(@args, @_) },
  0            
38             sub {
39 0 0   0     if (my $else = $else{$f}) {
40 0           $else->(@_)
41             } else {
42 0           $f_type->AWAIT_NEW_FAIL(@_)
43             }
44             },
45 0           );
46 0 0         if (my $start_obj = $start{$self}) {
47 0           $then{$f} = $start{$f} = $start_obj;
48             }
49 0           return $f;
50             }
51              
52             sub AUTOLOAD {
53 0     0     my ($self, @args) = @_;
54 0           my ($method) = our $AUTOLOAD =~ /^then::(.+)$/;
55 0           $self->then::_($method => @args);
56             }
57              
58             package else;
59              
60             sub else::_ {
61 0     0     my ($self, $method, @args) = @_;
62             Carp::croak "Can only call else on result of start:: -> then::"
63 0 0         unless my $start_obj = $then{$self};
64 0     0     $else{$self} = sub { $start_obj->$method(@args, @_) };
  0            
65 0           return $self;
66             }
67              
68             sub AUTOLOAD {
69 0     0     my ($self, @args) = @_;
70 0           my ($method) = our $AUTOLOAD =~ /^else::(.+)$/;
71 0           $self->else::_($method => @args);
72             }
73              
74             package catch;
75              
76             sub catch::_ {
77 0     0     my ($self, $method, @args) = @_;
78             Carp::croak "Can only call catch on start:: or start:: -> then:: object"
79 0 0         unless my $start_obj = $start{$self};
80 0     0     $self->catch(sub { $start_obj->$method(@args, @_) });
  0            
81             }
82              
83             sub AUTOLOAD {
84 0     0     my ($self, @args) = @_;
85 0           my ($method) = our $AUTOLOAD =~ /^catch::(.+)$/;
86 0           $self->catch::_($method => @args);
87             }
88              
89             package await;
90              
91             sub this {
92 0     0 0   my ($self) = @_;
93 0 0         return $self->get if $self->can('get');
94 0 0         if ($self->isa('Mojo::Promise')) {
95             # This logic stolen from Mojo::Promis::Role::Get v0.1.2
96 0 0         Carp::croak "'get' cannot be called when the event loop is running"
97             if $self->ioloop->is_running;
98 0           my (@result, $rejected);
99 0     0     $self->then(sub { @result = @_ }, sub { $rejected = 1; @result = @_ })
  0            
  0            
100 0           ->wait;
101 0 0         if ($rejected) {
102 0   0       my $reason = $result[0] // 'Promise was rejected';
103 0 0 0       die $reason if ref $reason or $reason =~ m/\n\z/;
104 0           Carp::croak $reason;
105             }
106 0 0         return wantarray ? @result : $result[0];
107             }
108 0           Carp::croak "Don't know how to await::this for $self";
109             }
110              
111             sub await::_ {
112 0     0     my ($self, $method, @args) = @_;
113 0 0         if ($self eq 'await') {
114 0           Carp::croak "Call of '${method} await' should be '${method} +await'";
115             }
116 0 0         my $f = ($self->can('then')
117             ? $self->then::_($method, @args)
118             : $self->$method(@args)
119             );
120 0           $f->await::this;
121             }
122              
123             sub AUTOLOAD {
124 0     0     my ($self, @args) = @_;
125 0           my ($method) = our $AUTOLOAD =~ /^await::(.+)$/;
126 0           $self->await::_($method => @args);
127             }
128              
129             1;
130              
131             =head1 NAME
132              
133             Async::Methods - Namespaced sugar methods for async/await and future/promise based code
134              
135             =head1 SYNOPSIS
136              
137             use Mojo::UserAgent;
138            
139             my $ua = Mojo::UserAgent->new;
140            
141             # Normal synchronous code
142            
143             print $ua->get('http://trout.me.uk/')->result->body;
144            
145             # Equivalent code running synchronously atop promises
146            
147             print $ua->get_p('http://trout.me.uk')->then::result->await::body;
148            
149             # Equivalent code within an async subroutine
150            
151             use Mojo::Base -async_await, -signatures;
152            
153             async sub fetch ($url) {
154             await $ua->get_p($url)->then::result->then::body;
155             }
156            
157             print fetch($url)->await::this;
158              
159             =head1 DESCRIPTION
160              
161             L provides a set of helper methods operating via namespace
162             that make chaining together asynchronous methods easier. This is not at all
163             meant to be a replacement for the C and C keywords available
164             via L or the C<-async_await> flag to L and
165             in fact is largely meant to be used I such facilities.
166              
167             Note that in the following code I use C<$p> for example variables but they
168             can be L or L objects or (hopefully) objects of any
169             other class that provides a similar interface.
170              
171             Note that methods of each type provided can be called three ways:
172              
173             $obj->the_type::some_method(@args);
174              
175             will call C on a relevant object, and is effectively simply
176             sugar for the second type,
177              
178             $obj->the_type::_(some_method => @args);
179              
180             which calls the method name given in its first argument (yes, this means that
181             you can't use the first syntax to call a method called C<_> but the author of
182             this module strongly suspects that won't be an inconvience in most cases).
183              
184             Thirdly, to match perl's capacity to allow <$obj->$cb(@args)> as a syntax, you
185             can also call:
186              
187             $obj->the_type::_(sub { ... } => @args);
188             $obj->the_type::_($cb => @args);
189              
190             to call that code reference as a method.
191              
192             =head1 METHODS
193              
194             =head2 start::
195              
196             my $p = $obj->start::some_method(@args);
197             my $p = $obj->start::_(some_method => @args);
198             my $p = $obj->start::_(sub { ... } => @args);
199              
200             L methods don't do anything special in and of themselves but
201             register the C<$obj> with L to allow L and
202             L to work correctly (see their documentation below for why you
203             might find that useful). Other than the registration part, this is
204             entirely equivalent to
205              
206             my $p = $obj->some_method(@args);
207              
208             =head2 then::
209              
210             my $then_p = $p->then::some_method(@args);
211             my $then_p = $p->then::_(some_method => @args);
212             my $then_p = $p->then::_(sub { ... } => @args);
213              
214             L allows for chaining an additional method call from the return
215             value of the previous promise (assuming it's successful). As such, on its own
216             this is equivalent to
217              
218             my $then_p = $p->then(
219             sub ($obj, @rest) { $obj->some_method(@args, @rest)) }
220             );
221              
222             Note that L does not require anything special of the promise upon
223             which it's called to provide the base functionality, but I need to be
224             called on the result of something rooted in L if you want to be
225             able to chain L or L from the return value.
226              
227             =head2 else::
228              
229             my $else_p = $p->else::some_method(@args);
230             my $else_p = $p->else::_(some_method => @args);
231             my $else_p = $p->else::_(sub { ... } => @args);
232              
233              
234             L must be called on the result of a L chained to a
235             L, and provides a callback if the Led method fails,
236             invoked on the I invocant. This makes it the "other half" of
237             L' support for two-arg C<<->then>>, so:
238              
239             my $else_p = $obj->start::one(@args1)
240             ->then::two(@args2)
241             ->else::three(@args3);
242              
243             is functionally equivalent to:
244              
245             my $else_p = $obj->one(@args1)
246             ->then(
247             sub ($then_obj, @then_rest) {
248             $then_obj->two(@args2, @then_rest)
249             },
250             sub (@error) {
251             $obj->three(@args3, @error)
252             },
253             );
254              
255             which the author hopes explains why you might, on the whole, not really
256             mind being forced to type L.
257              
258             Note that because L always resolves to the second argument to a
259             two-arg C call, it can't be used in isolation. Fortunately, we already
260             provide L for that, which is documented next.
261              
262             =head2 catch::
263              
264             my $catch_p = $p->catch::some_method(@args);
265             my $catch_p = $p->catch::_(some_method => @args);
266             my $catch_p = $p->catch::_(sub { ... } => @args);
267              
268             L can be called on the result of either a L call or
269             a L -> L chain, and will catch any/all errors produced
270             up to this point, as opposed to L which catches errors I
271             the preceding L call.
272              
273             As such, morally equivalent to:
274              
275             my $catch_p = $obj->start::whatever(...)
276             ->catch(sub ($obj, @error) {
277             $obj->some_method(@args, @error)
278             });
279              
280             =head2 await::
281              
282             my $ret = $p->await::this;
283              
284             C is simple generic sugar for (at top level of your code outside
285             of an already-running event loop) spinning the event loop until the promise
286             completes and then either returning the result on success or Cing with
287             the error on failure. For a future, it's equivalent to
288              
289             my $ret = $f->get;
290              
291             but if called on a L loads L and uses
292             that to complete the operation, so C can be called on either and
293             still provides a uniform interface. Assuming you install
294             L if you need it of course - otherwise you'll get
295             an exception from the relevant C call.
296              
297             my $ret = $p->await::some_method(@args);
298             my $ret = $p->await::_(some_method => @args);
299             my $ret = $p->await::_(sub { ... } => @args);
300              
301             L requires absolutely nothing of the promise upon which it's called,
302             and other than the special case of C is equivalent to
303              
304             my $ret = $p->then::some_method(@args)->await::this;
305              
306             Hopefully obvious caveat: If you want to await a method called C you'll
307             need to call one of
308              
309             my $ret = $p->then::this(@args)->await::this;
310             my $ret = $p->await::_(this => @args);
311              
312             but C did not strike the author as a sufficiently common method name
313             to be a deal-breaker in practice.
314              
315             =head1 AUTHOR
316              
317             mst - Matt S. Trout (cpan:MSTROUT)
318              
319             =head1 CONTRIBUTORS
320              
321             Grinnz - Dan Book (cpan:DBOOK)
322              
323             =head1 COPYRIGHT
324              
325             Copyright (c) 2020 the Async::Methods L and L
326             as listed above.
327              
328             =head1 LICENSE
329              
330             This library is free software and may be distributed under the same terms
331             as perl itself.