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.03';
4             # ABSTRACT: An implementation of Promises in Perl
5              
6 11     11   58 use strict;
  11         16  
  11         253  
7 11     11   38 use warnings;
  11         16  
  11         288  
8              
9 11     11   47 use Scalar::Util qw[ blessed reftype ];
  11         15  
  11         1090  
10 11     11   54 use Carp qw[ confess carp ];
  11         22  
  11         476  
11              
12 11     11   3845 use Promises::Promise;
  11         22  
  11         298  
13              
14 11     11   56 use constant IN_PROGRESS => 'in progress';
  11         11  
  11         785  
15 11     11   58 use constant RESOLVED => 'resolved';
  11         14  
  11         390  
16 11     11   49 use constant REJECTED => 'rejected';
  11         25  
  11         15067  
17              
18             sub new {
19 158     158 1 1389 my $class = shift;
20              
21 158 100       227 my $caller = $Promises::WARN_ON_UNHANDLED_REJECT ? _trace() : undef ;
22              
23 158         448 bless {
24             _caller => $caller,
25             resolved => [],
26             rejected => [],
27             status => IN_PROGRESS
28             } => $class;
29             }
30              
31             sub _trace {
32 9     9   9 my $i = 0;
33              
34 9         17 while( my( $package, $filename, $line ) = caller($i++) ) {
35 30 100       951 return [ $filename, $line ] unless $package =~ /^Promises/;
36             }
37              
38             return
39 0         0 }
40              
41 127     127 1 262 sub promise { Promises::Promise->new(shift) }
42 0     0 1 0 sub status { (shift)->{'status'} }
43 255     255 1 450 sub result { (shift)->{'result'} }
44              
45             # predicates for all the status possibilities
46 281     281 1 706 sub is_in_progress { (shift)->{'status'} eq IN_PROGRESS }
47 242     242 1 461 sub is_resolved { (shift)->{'status'} eq RESOLVED }
48 254     254 1 580 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 1115 my $self = shift;
58              
59 133 50       177 die "Cannot resolve. Already " . $self->status
60             unless $self->is_in_progress;
61              
62 133         253 $self->{'result'} = [@_];
63 133         177 $self->{'status'} = RESOLVED;
64 133         274 $self->_notify;
65 133         205 $self;
66             }
67              
68             sub reject {
69 25     25 1 280 my $self = shift;
70 25 50       36 die "Cannot reject. Already " . $self->status
71             unless $self->is_in_progress;
72              
73 25         50 $self->{'result'} = [@_];
74 25         37 $self->{'status'} = REJECTED;
75 25         54 $self->_notify;
76 25         243 $self;
77             }
78              
79             sub then {
80 120     120 1 172 my $self = shift;
81 120         205 my ( $callback, $error ) = $self->_callable_or_undef(@_);
82              
83 120         256 my $d = ( ref $self )->new;
84 120         162 push @{ $self->{'resolved'} } => $self->_wrap( $d, $callback, 'resolve' );
  120         221  
85 120         152 push @{ $self->{'rejected'} } => $self->_wrap( $d, $error, 'reject' );
  120         197  
86              
87 120 100       189 $self->_notify unless $self->is_in_progress;
88 120         196 $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 3 my $self = shift;
114 3         5 my ($callback) = $self->_callable_or_undef(@_);
115              
116 3         8 my $d = ( ref $self )->new;
117              
118 3 50       9 if (defined $callback) {
119 3         5 my ( @result, $method );
120 3     3   9 my $finish_d = sub { $d->$method(@result); () };
  3         13  
  3         4  
121              
122             my $f = sub {
123 3     3   7 ( $method, @result ) = @_;
124 3         4 local $@;
125 3         4 my ($p) = eval { $callback->(@result) };
  3         24  
126 3 50 33     283 if ( $p && blessed $p && $p->can('then') ) {
      33        
127 0         0 return $p->then( $finish_d, $finish_d );
128             }
129 3         19 $finish_d->();
130 3         18 ();
131 3         8 };
132              
133 3     2   11 push @{ $self->{'resolved'} } => sub { $f->( 'resolve', @_ ) };
  3         20  
  2         4  
134 3     1   4 push @{ $self->{'rejected'} } => sub { $f->( 'reject', @_ ) };
  3         8  
  1         2  
135              
136 3 100       8 $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   349 my ( $self, $d, $f, $method ) = @_;
170              
171 9     9   12 return sub { $d->$method( @{ $self->result } ) }
  9         11  
172 240 100       483 unless defined $f;
173              
174             return sub {
175 111     111   118 local $@;
176 111         127 my ( @results, $error );
177             eval {
178 111         114 @results = do { $f->(@_) };
  111         191  
179 107         6177 1;
180             }
181 111 50       170 || do { $error = defined $@ ? $@ : 'Unknown reason' };
  4 100       485  
182              
183 111 100 100     462 if ($error) {
    100 66        
184 4         24 $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         25 sub { $d->resolve(@_); () },
  13         20  
192 4         9 sub { $d->reject(@_); () },
  4         7  
193 17         83 );
194             }
195             else {
196 90         164 $d->resolve(@results);
197             }
198 111         506 return;
199 154         451 };
200             }
201              
202             sub _notify {
203 242     242   306 my ($self) = @_;
204              
205 242 100       303 my $cbs = $self->is_resolved ? $self->{resolved} : $self->{rejected};
206              
207 242   100     350 $self->{_reject_was_handled} = $self->is_rejected && @$cbs;
208              
209 242         410 $self->{'resolved'} = [];
210 242         438 $self->{'rejected'} = [];
211              
212 242         356 return $self->_notify_backend( $cbs, $self->result );
213             }
214              
215             sub _notify_backend {
216 242     242   316 my ( $self, $cbs, $result ) = @_;
217 242         435 $_->(@$result) foreach @$cbs;
218             }
219              
220             sub _callable_or_undef {
221 123     123   132 shift;
222             map {
223             # coderef or object overloaded as coderef
224 123 100 66     162 ref && reftype $_ eq 'CODE' || blessed $_ && $_->can('()')
  158         692  
225             ? $_
226             : undef
227             } @_;
228             }
229              
230              
231             1;
232              
233             __END__