File Coverage

blib/lib/Async/Methods.pm
Criterion Covered Total %
statement 12 70 17.1
branch 0 22 0.0
condition 0 5 0.0
subroutine 4 20 20.0
pod 0 1 0.0
total 16 118 13.5


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