File Coverage

blib/lib/AnyEvent/Promise.pm
Criterion Covered Total %
statement 74 76 97.3
branch 5 6 83.3
condition 2 3 66.6
subroutine 18 19 94.7
pod 7 7 100.0
total 106 111 95.5


line stmt bran cond sub pod time code
1             package AnyEvent::Promise;
2              
3 4     4   120986 use 5.008;
  4         15  
  4         159  
4 4     4   24 use strict;
  4         7  
  4         151  
5 4     4   18 use warnings FATAL => 'all';
  4         11  
  4         176  
6              
7 4     4   1850 use AnyEvent;
  4         6020  
  4         108  
8 4     4   3742 use Try::Tiny qw//;
  4         7440  
  4         310  
9 4     4   28 use Carp;
  4         7  
  4         495  
10              
11             =head1 NAME
12              
13             AnyEvent::Promise - Evented promises
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.02';
22              
23             =head1 SYNOPSIS
24              
25             Avoid the evented pyramid of doom!
26              
27             use AnyEvent::Promise;
28             use AnyEvent::Redis;
29              
30             my $redis = AnyEvent::Redis->new();
31              
32             my $p = promise(sub {
33             $redis->get('test');
34             })->then(sub {
35             $redis->set('test', shift);
36             })->then(sub {
37             $redis->get('test');
38             })->then(sub {
39             say shift;
40             })->catch(sub {
41             say 'I failed!';
42             say @_;
43             })->fulfill;
44              
45             =head1 DESCRIPTION
46              
47             L allows evented interfaces to be chained, taking away some
48             of the redundancy of layering L condition variable callbacks.
49              
50             A promise is created using L or the exported
51             L helper function. These will both return a promise instance and add
52             the callback function as the start of the promise chain. Each call to L
53             on the promise instance will add another subroutine which returns a condition
54             variable to the chain.
55              
56             The promise callback chain won't start until L or L is
57             called on the instance. Calling L or L will start the callback
58             chain and return the promise guarding condvar, which is fulfilled after the last
59             callback on the chain returns. Similarily, L will start the chain, but
60             will block until the guarding condvar is fulfilled.
61              
62             Errors in the callbacks can be caught by setting an exception handler via the
63             L method on the promise instance. This method will catch exceptions
64             raised from L objects and exceptions raised in blocks provided to
65             L. If an error is encountered in the chain, an exception will be thrown
66             and the rest of the chain will be skipped, jumping straight to the catch
67             callback.
68              
69             =head1 EXPORT
70              
71             =head2 promise($cb)
72              
73             Start promise chain with callback C<$cb>. This function is a shortcut to
74             L, and returns a promise object with the callback
75             attached.
76              
77             =cut
78 1     1 1 14 sub promise { AnyEvent::Promise->new(@_) }
79              
80             sub import {
81 4     4   21 no strict 'refs'; ## no critic (ProhibitNoStrict)
  4         8  
  4         3543  
82 5     5   3486 *{caller() . '::promise'} = \&promise;
  5         8390  
83             }
84              
85             =head1 METHODS
86              
87             =head2 new($cb)
88              
89             Create an instance of a promise, start the chain off with callback C<$cb>. See
90             L for information on passing in a callback and condvar.
91              
92             =cut
93             sub new {
94 11     11 1 1815 my ($class, $cb) = @_;
95              
96 11         70 my $self = bless {
97             guard => undef,
98             initial => undef,
99             fulfill => undef,
100             reject => undef,
101             rejected => 0
102             }, $class;
103              
104 11         224 $self->{guard} = AnyEvent->condvar;
105 11         23184 $self->{initial} = AnyEvent->condvar;
106              
107 11         270 my $reject = AnyEvent->condvar;
108             $reject->cb(sub {
109 0     0   0 carp shift->recv;
110 0         0 $self->{guard}->send;
111 11         111 });
112 11         331 $self->{reject} = $reject;
113              
114 11         33 $self->then($cb);
115              
116 11         54 return $self;
117             }
118              
119             =head2 then($cb)
120              
121             Add callback C<$cb> on to the promise chain.
122              
123             This callback will receive the return of the previous callback -- i.e. the
124             callback will receive the value sent by the previous condvar directly. In order
125             to continue the promise chain, the callback should return a condvar.
126              
127             Instead of:
128              
129             my $cv = $redis->get('test');
130             $cv->cb(sub {
131             my $ret = shift->recv;
132             my $cv2 = $redis->set('test', $ret);
133             $cv2->cb(sub {
134             my $cv3 = $redis->get('test');
135             $cv3->cb(sub {
136             my $ret3 = shift->recv;
137             printf("Got a value: %s\n", $ret3);
138             });
139             });
140             });
141             $cv->recv;
142              
143             .. a promise chain can be used, by chaining calls to the L method:
144              
145             my $promise = AnyEvent::Promise->new(sub {
146             $redis->get('test');
147             })->then(sub {
148             my $val = shift;
149             $redis->set('test', $val);
150             })->then(sub {
151             $redis->get('test');
152             })->then(sub {
153             my $val = shift;
154             printf("Got a value: %s\n", $val)
155             })->fulfill;
156              
157             =cut
158             sub then {
159 23     23 1 1238 my ($self, $fn) = @_;
160              
161 23 50       61 return $self
162             if ($self->{rejected});
163              
164 23         85 $self->{guard}->begin;
165              
166 23         106 my $cvin = $self->{fulfill};
167 23 100       52 if (!defined $cvin) {
168 11         31 $cvin = $self->{initial};
169             }
170              
171 23         494 my $cvout = AnyEvent->condvar;
172             $cvin->cb(sub {
173 15     15   165 my $thenret = shift;
174             Try::Tiny::try {
175 15         431 my $ret = $thenret->recv;
176 15         120 my $cvret = $fn->($ret);
177 13 100 66     4051 if ($cvret and ref $cvret eq 'AnyEvent::CondVar') {
178             $cvret->cb(sub {
179 10         1997588 my $ret_inner = shift;
180             Try::Tiny::try {
181 10         325 $cvout->send($ret_inner->recv);
182 9         216 $self->{guard}->end;
183             }
184             Try::Tiny::catch {
185 1         231 $self->{rejected} = 1;
186 1         4 $self->{reject}->send(@_);
187             }
188 10         55 });
  10         73  
189             }
190             else {
191 3         16 $cvout->send($cvret);
192 3         153 $self->{guard}->end;
193             }
194             }
195             Try::Tiny::catch {
196 2         1030 $self->{rejected} = 1;
197 2         11 $self->{reject}->send(@_);
198             }
199 23         187 });
  15         108  
200 23         154 $self->{fulfill} = $cvout;
201              
202 23         65 return $self;
203             }
204              
205             =head2 catch($cb)
206              
207             Catch raised errors in the callback chain. Exceptions in the promise chain will
208             jump up to this catch callback, bypassing any other callbacks in the promise
209             chain. The error caught by L will be sent as arguments to the
210             callback C<$cb>.
211              
212             =cut
213             sub catch {
214 3     3 1 2435 my ($self, $fn) = @_;
215              
216             $self->{reject}->cb(sub {
217 3     3   25 my @err = shift->recv;
218 3         25 $fn->(@err);
219 3         1171 $self->{guard}->send;
220 3         16 });
221              
222 3         30 return $self;
223             }
224              
225             =head2 condvar(...)
226              
227             Trigger the start of the promise chain and return the guard condvar from the
228             promise. The guard condvar is fulfilled either after the last callback returns
229             or an exception is encountered somewhere in the chain.
230              
231             All arguments passed into L are sent to the first condvar in the
232             promise chain.
233              
234             =cut
235             sub condvar {
236 7     7 1 11 my $self = shift;
237 7         36 $self->{initial}->send(@_);
238             $self->{fulfill}->cb(sub {
239 4     4   50 $self->{guard}->send;
240 7         198 });
241 7         50 return $self->{guard};
242             };
243              
244             =head2 cv(...)
245              
246             Alias of L
247              
248             =cut
249 1     1 1 410 sub cv { condvar(@_) }
250              
251             =head2 fulfill(...)
252              
253             Similar to L, trigger the start of the promise chain, but C on
254             the returned condvar as well.
255              
256             =cut
257             sub fulfill {
258 6     6 1 471 my $self = shift;
259 6         19 my $cv = $self->condvar(@_);
260 6         34 $cv->recv;
261             }
262              
263             =head1 AUTHOR
264              
265             Anthony Johnson, C<< >>
266              
267             =head1 LICENSE AND COPYRIGHT
268              
269             Copyright 2013 Anthony Johnson.
270              
271             This program is distributed under the MIT (X11) License:
272             L
273              
274             Permission is hereby granted, free of charge, to any person
275             obtaining a copy of this software and associated documentation
276             files (the "Software"), to deal in the Software without
277             restriction, including without limitation the rights to use,
278             copy, modify, merge, publish, distribute, sublicense, and/or sell
279             copies of the Software, and to permit persons to whom the
280             Software is furnished to do so, subject to the following
281             conditions:
282              
283             The above copyright notice and this permission notice shall be
284             included in all copies or substantial portions of the Software.
285              
286             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
287             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
288             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
289             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
290             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
291             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
292             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
293             OTHER DEALINGS IN THE SOFTWARE.
294              
295             =cut
296              
297             1;