File Coverage

blib/lib/Data/Promise.pm
Criterion Covered Total %
statement 75 79 94.9
branch 21 24 87.5
condition n/a
subroutine 16 22 72.7
pod 6 9 66.6
total 118 134 88.0


line stmt bran cond sub pod time code
1             package Data::Promise;
2              
3             =head1 NAME
4              
5             Data::Promise - simple promise like interface
6              
7             =head1 SYNOPSIS
8              
9             use Modern::Perl;
10             use Data::Promose;
11              
12             my $p=new Data::Promise(cb=>sub {
13             my ($resolve,$reject)=@_;
14              
15             if(...) {
16             # pass context
17             $resolve->('ok');
18             } else {
19             $reject->('something went wrong');
20             }
21             });
22              
23             sub pass_function { ... }
24             sub fail_function { ... }
25             $p->then(\&pass_function,\&fail_function);
26              
27              
28             # delayed example
29             my $p=new Data::Promise(
30             delayed=>1,
31             cb=>sub {
32              
33             if(...) {
34             # pass context
35             $resolve->('ok');
36             } else {
37             $reject->('something went wrong');
38             }
39             });
40              
41             $p->then(\&pass_function,\&fail_function);
42             # pass and fail functions will not be called until now
43             $p->do_resolve;
44              
45             ## create a rejected promise
46             my $p=Data::Promise->reject(42);
47              
48             # you can be sure your fail funtion will be called
49             $p->then(\&pass_function,\&fail_function);
50              
51             ## create a resolved promise
52             my $p=Data::Promise->resolve(42);
53              
54             # you can be sure your pass funtion will be called
55             $p->then(\&pass_function,\&fail_function);
56              
57             =head1 DESCRIPTION
58              
59             A light and simple Promise object based on the current es6 implementation. This promise object class was written to mimic how promise(s) are implemnted in the wild. This may or may not be the class you are looking for.
60              
61             =cut
62              
63             our $VERSION=0.001;
64              
65 1     1   780 use Modern::Perl;
  1         2  
  1         7  
66 1     1   742 use Moo;
  1         11072  
  1         4  
67 1     1   1981 use MooX::Types::MooseLike::Base qw(:all);
  1         6542  
  1         401  
68              
69 1     1   512 use namespace::clean;
  1         11341  
  1         9  
70              
71             =head1 OO Constructor Arguments
72              
73             =over 4
74              
75             =item * cb=>sub { my ($resovle,$reject)=@_ }
76              
77             The callback function used to resolve the object. If no function is passed in then the object will never resolve!
78              
79             =cut
80              
81             has cb=>(
82             isa=>CodeRef,
83             default=>\&_build_stub,
84             is=>'ro',
85             );
86              
87             =item * delayed=>0|1
88              
89             This enables or disables manual control over when your cb function will be called. The default is false.
90              
91             =cut
92              
93             has delayed=>(
94             isa=>Bool,
95             is=>'ro',
96             default=>0,
97             );
98              
99             =back
100              
101             =cut
102              
103             has _jobs=>(
104             isa=>ArrayRef,
105             is=>'ro',
106             default=>sub {[]},
107             );
108              
109             has _finally=>(
110             isa=>ArrayRef,
111             is=>'ro',
112             default=>sub {[]},
113             );
114              
115             has _then_catch=>(
116             isa=>ArrayRef,
117             is=>'ro',
118             default=>sub {[]},
119             );
120              
121             has _pending=>(
122             is=>'rw',
123             );
124              
125             has _result=>(
126             is=>'rw',
127             );
128              
129             sub _build_stub {
130 0     0   0 return \&_default_stub
131             }
132              
133       0     sub _default_stub { }
134              
135             =head1 Promise functions
136              
137             =head2 if($p->pending) { ... }
138              
139             Used as a state checking interface, this method returns true if the promise is still being resolved, false if it is not.
140              
141             =cut
142              
143             sub pending {
144 30     30 0 1505 my ($self)=@_;
145 30 100       134 return defined($self->_pending) ? 0 : 1;
146             }
147              
148             =head2 my $p=$p->then(\&resolve,\&reject)
149              
150             This method provides a way to attach functions that will be called when the object is either rejected or resovled.
151              
152             =cut
153              
154             sub then {
155 6     6 1 14 my ($self,$resolve,$reject)=@_;
156              
157 6 100   0   21 $resolve=sub {} unless defined($resolve);
158 6 100   0   16 $reject=sub {} unless defined($reject);
159 6 100       14 if($self->pending) {
160 2         5 push @{$self->_jobs},[$resolve,$reject];
  2         8  
161             } else {
162 4 100       14 my $code=$self->_pending==0 ? $resolve : $reject;
163 4         5 eval { $code->(@{$self->_result}) };
  4         6  
  4         13  
164             }
165 6         27 return $self;
166             }
167              
168             =head2 my $p=$p->catch(\&reject)
169              
170             This is really a wrapper function for: $p->then(undef,\&reject);
171              
172             =cut
173              
174             sub catch {
175 0     0 1 0 my ($self,$code)=@_;
176 0         0 $self->then(undef,$code);
177             }
178              
179             =head2 my $p=Data::Promise->reject(@args)
180              
181             Creates a rejected promise with @args as the rejected data.
182              
183             =cut
184              
185             sub reject {
186 1     1 1 28 my ($class,@args)=@_;
187             return __PACKAGE__->new(cb=>sub {
188 1     1   4 my ($pass,$fail)=@_;
189 1         3 $fail->(@args);
190 1         23 });
191             }
192              
193             =head2 my $p=Data::Promise->resolve(@args)
194              
195             Creates a resolved promise with @args as the resolved data.
196              
197             =cut
198              
199             sub resolve {
200 1     1 1 28 my ($class,@args)=@_;
201             return __PACKAGE__->new(cb=>sub {
202 1     1   3 my ($pass,$fail)=@_;
203 1         3 $pass->(@args);
204 1         22 });
205             }
206              
207             =head2 my $p=$p->finally(sub {});
208              
209             Allows the addition of functions that will be called once the object is resolved. The functions will recive no arguments, and are called reguardless of the resolved or rejected state.
210              
211             =cut
212              
213             sub finally {
214 4     4 1 8 my ($self,$code)=@_;
215              
216 4 50   0   10 $code=sub {} unless defined($code);
217              
218 4 100       8 if($self->pending) {
219 2         6 push @{$self->_finally},$code;
  2         7  
220             } else {
221 2         5 eval { $code->() }
  2         12  
222             }
223 4         24 return $self;
224             }
225              
226             sub _resolver {
227 10     10   20 my ($self,$col)=@_;
228             return sub {
229              
230 6 100   6   17 return unless $self->pending;
231 4         9 my $args=[@_];
232 4         13 $self->_result($args);
233 4         9 $self->_pending($col);
234 4         8 foreach my $funcs (@{$self->_jobs}) {
  4         12  
235 2         4 eval {
236 2         3 $funcs->[$col]->(@{$args});
  2         6  
237             };
238             }
239             }
240 10         46 }
241              
242             sub BUILD {
243 4     4 0 391 my ($self)=@_;
244              
245 4 100       24 return if $self->delayed;
246 2         11 $self->do_resolve;
247             }
248              
249             =head2 my $p=$p->do_resolve
250              
251             When the promise is constructed in a delayed state, this method must be called to activate the cb method.
252              
253             =cut
254              
255             sub do_resolve {
256 6     6 1 16 my ($self)=@_;
257              
258 6 100       13 return unless $self->pending;
259 4         12 my ($pass,$fail)=($self->_resolver(0),$self->_resolver(1));
260 4         6 eval {
261 4         14 $self->cb->(
262             $pass,
263             $fail,
264             );
265             };
266 4 50       19 if($@) {
267 0         0 $fail->($@);
268             }
269 4         5 foreach my $f (@{$self->_finally}) {
  4         10  
270 2         3 eval {
271 2         6 $f->();
272             };
273             }
274              
275             # clean up all code refs
276 4         8 @{$self->_jobs}=();
  4         16  
277 4         6 @{$self->_finally}=();
  4         10  
278 4         30 return $self;
279             }
280              
281             sub DEMOLISH {
282 4     4 0 2870 my ($self)=@_;
283 4 50       13 return unless defined($self);
284 4         8 %{$self}=();
  4         30  
285 4         68 undef $self;
286             }
287              
288             =head1 AUTHOR
289              
290             Michael Shipper
291              
292             =cut
293              
294             1;