File Coverage

blib/lib/Promises/Deferred.pm
Criterion Covered Total %
statement 114 146 78.0
branch 25 42 59.5
condition 12 18 66.6
subroutine 30 42 71.4
pod 19 19 100.0
total 200 267 74.9


line stmt bran cond sub pod time code
1             package Promises::Deferred;
2             our $AUTHORITY = 'cpan:YANICK';
3             $Promises::Deferred::VERSION = '1.04';
4             # ABSTRACT: An implementation of Promises in Perl
5              
6 11     11   76 use strict;
  11         18  
  11         302  
7 11     11   48 use warnings;
  11         15  
  11         316  
8              
9 11     11   55 use Scalar::Util qw[ blessed reftype ];
  11         22  
  11         830  
10 11     11   60 use Carp qw[ confess carp ];
  11         26  
  11         547  
11              
12 11     11   4355 use Promises::Promise;
  11         27  
  11         346  
13              
14 11     11   65 use constant IN_PROGRESS => 'in progress';
  11         18  
  11         948  
15 11     11   65 use constant RESOLVED => 'resolved';
  11         19  
  11         438  
16 11     11   54 use constant REJECTED => 'rejected';
  11         29  
  11         17057  
17              
18             sub new {
19 158     158 1 1340 my $class = shift;
20              
21 158 100       268 my $caller = $Promises::WARN_ON_UNHANDLED_REJECT ? _trace() : undef ;
22              
23 158         522 bless {
24             _caller => $caller,
25             resolved => [],
26             rejected => [],
27             status => IN_PROGRESS
28             } => $class;
29             }
30              
31             sub _trace {
32 9     9   13 my $i = 0;
33              
34 9         22 while( my( $package, $filename, $line ) = caller($i++) ) {
35 30 100       1179 return [ $filename, $line ] unless $package =~ /^Promises/;
36             }
37              
38             return
39 0         0 }
40              
41 127     127 1 286 sub promise { Promises::Promise->new(shift) }
42 0     0 1 0 sub status { (shift)->{'status'} }
43 255     255 1 453 sub result { (shift)->{'result'} }
44              
45             # predicates for all the status possibilities
46 281     281 1 701 sub is_in_progress { (shift)->{'status'} eq IN_PROGRESS }
47 242     242 1 469 sub is_resolved { (shift)->{'status'} eq RESOLVED }
48 254     254 1 668 sub is_rejected { (shift)->{'status'} eq REJECTED }
49 0     0 1 0 sub is_done { ! $_[0]->is_in_progress }
50              
51             # the three possible states according to the spec ...
52 0     0 1 0 sub is_unfulfilled { (shift)->is_in_progress }
53 0     0 1 0 sub is_fulfilled { $_[0]->is_resolved }
54 0     0 1 0 sub is_failed { $_[0]->is_rejected }
55              
56             sub resolve {
57 133     133 1 1292 my $self = shift;
58              
59 133 50       195 die "Cannot resolve. Already " . $self->status
60             unless $self->is_in_progress;
61              
62 133         259 $self->{'result'} = [@_];
63 133         185 $self->{'status'} = RESOLVED;
64 133         306 $self->_notify;
65 133         220 $self;
66             }
67              
68             sub reject {
69 25     25 1 395 my $self = shift;
70 25 50       46 die "Cannot reject. Already " . $self->status
71             unless $self->is_in_progress;
72              
73 25         61 $self->{'result'} = [@_];
74 25         48 $self->{'status'} = REJECTED;
75 25         70 $self->_notify;
76 25         301 $self;
77             }
78              
79             sub then {
80 120     120 1 190 my $self = shift;
81 120         200 my ( $callback, $error ) = $self->_callable_or_undef(@_);
82              
83 120         270 my $d = ( ref $self )->new;
84 120         196 push @{ $self->{'resolved'} } => $self->_wrap( $d, $callback, 'resolve' );
  120         245  
85 120         166 push @{ $self->{'rejected'} } => $self->_wrap( $d, $error, 'reject' );
  120         206  
86              
87 120 100       217 $self->_notify unless $self->is_in_progress;
88 120         214 $d->promise;
89             }
90              
91             sub chain {
92 0     0 1 0 my $self = shift;
93 0         0 $self = $self->then($_) for @_;
94 0         0 return $self;
95             }
96              
97             sub catch {
98 1     1 1 2 my $self = shift;
99 1         3 $self->then( undef, @_ );
100             }
101              
102             sub done {
103 0     0 1 0 my $self = shift;
104 0         0 my ( $callback, $error ) = $self->_callable_or_undef(@_);
105 0 0       0 push @{ $self->{'resolved'} } => $callback if defined $callback;
  0         0  
106 0 0       0 push @{ $self->{'rejected'} } => $error if defined $error;
  0         0  
107              
108 0 0       0 $self->_notify unless $self->is_in_progress;
109 0         0 ();
110             }
111              
112             sub finally {
113 3     3 1 5 my $self = shift;
114 3         9 my ($callback) = $self->_callable_or_undef(@_);
115              
116 3         22 my $d = ( ref $self )->new;
117              
118 3 50       11 if (defined $callback) {
119 3         6 my ( @result, $method );
120 3     3   12 my $finish_d = sub { $d->$method(@result); () };
  3         15  
  3         6  
121              
122             my $f = sub {
123 3     3   7 ( $method, @result ) = @_;
124 3         7 local $@;
125 3         6 my ($p) = eval { $callback->(@result) };
  3         29  
126 3 50 33     351 if ( $p && blessed $p && $p->can('then') ) {
      33        
127 0         0 return $p->then( $finish_d, $finish_d );
128             }
129 3         24 $finish_d->();
130 3         23 ();
131 3         11 };
132              
133 3     2   5 push @{ $self->{'resolved'} } => sub { $f->( 'resolve', @_ ) };
  3         26  
  2         8  
134 3     1   6 push @{ $self->{'rejected'} } => sub { $f->( 'reject', @_ ) };
  3         10  
  1         2  
135              
136 3 100       9 $self->_notify unless $self->is_in_progress;
137             }
138 3         7 $d->promise;
139              
140             }
141              
142             sub timeout {
143 0     0 1 0 my ( $self, $timeout ) = @_;
144              
145 0 0       0 unless( $self->can('_timeout') ) {
146 0         0 carp "timeout mechanism not implemented for Promise backend ", ref $self;
147 0         0 return $self->promise;
148             }
149              
150 0         0 my $deferred = ref($self)->new;
151              
152             my $cancel = $deferred->_timeout($timeout, sub {
153 0 0   0   0 return if $deferred->is_done;
154 0         0 $deferred->reject( 'timeout' );
155 0         0 } );
156              
157             $self->finally( $cancel )->then(
158 0     0   0 sub { 'resolve', @_ },
159 0     0   0 sub { 'reject', @_ },
160             )->then(sub {
161 0     0   0 my( $action, @args ) = @_;
162 0 0       0 $deferred->$action(@args) unless $deferred->is_done;
163 0         0 });
164              
165 0         0 return $deferred->promise;
166             }
167              
168             sub _wrap {
169 240     240   391 my ( $self, $d, $f, $method ) = @_;
170              
171 9     9   15 return sub { $d->$method( @{ $self->result } ) }
  9         13  
172 240 100       495 unless defined $f;
173              
174             return sub {
175 111     111   125 local $@;
176 111         126 my ( @results, $error );
177             eval {
178 111         112 @results = do { $f->(@_) };
  111         224  
179 107         6053 1;
180             }
181 111 50       154 || do { $error = defined $@ ? $@ : 'Unknown reason' };
  4 100       479  
182              
183 111 100 100     480 if ($error) {
    100 66        
184 4         16 $d->reject($error);
185             }
186             elsif ( @results == 1
187             and blessed $results[0]
188             and $results[0]->can('then') )
189             {
190             $results[0]->then(
191 13         47 sub { $d->resolve(@_); () },
  13         21  
192 4         11 sub { $d->reject(@_); () },
  4         8  
193 17         149 );
194             }
195             else {
196 90         167 $d->resolve(@results);
197             }
198 111         600 return;
199 154         537 };
200             }
201              
202             sub _notify {
203 242     242   343 my ($self) = @_;
204              
205 242 100       329 my $cbs = $self->is_resolved ? $self->{resolved} : $self->{rejected};
206              
207 242   100     391 $self->{_reject_was_handled} = $self->is_rejected && @$cbs;
208              
209 242         423 $self->{'resolved'} = [];
210 242         478 $self->{'rejected'} = [];
211              
212 242         361 return $self->_notify_backend( $cbs, $self->result );
213             }
214              
215             sub _notify_backend {
216 242     242   329 my ( $self, $cbs, $result ) = @_;
217 242         805 $_->(@$result) foreach @$cbs;
218             }
219              
220             sub _callable_or_undef {
221 123     123   138 shift;
222             map {
223             # coderef or object overloaded as coderef
224 123 100 66     169 ref && reftype $_ eq 'CODE' || blessed $_ && $_->can('()')
  158         779  
225             ? $_
226             : undef
227             } @_;
228             }
229              
230              
231             1;
232              
233             __END__