File Coverage

blib/lib/Async/Methods.pm
Criterion Covered Total %
statement 12 60 20.0
branch 0 12 0.0
condition 0 3 0.0
subroutine 4 19 21.0
pod 0 1 0.0
total 16 95 16.8


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