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.02';
4             # ABSTRACT: An implementation of Promises in Perl
5              
6 11     11   73 use strict;
  11         18  
  11         312  
7 11     11   49 use warnings;
  11         19  
  11         359  
8              
9 11     11   65 use Scalar::Util qw[ blessed reftype ];
  11         21  
  11         821  
10 11     11   67 use Carp qw[ confess carp ];
  11         30  
  11         597  
11              
12 11     11   4408 use Promises::Promise;
  11         28  
  11         392  
13              
14 11     11   71 use constant IN_PROGRESS => 'in progress';
  11         21  
  11         956  
15 11     11   78 use constant RESOLVED => 'resolved';
  11         22  
  11         490  
16 11     11   60 use constant REJECTED => 'rejected';
  11         33  
  11         18654  
17              
18             sub new {
19 158     158 1 2041 my $class = shift;
20              
21 158 100       331 my $caller = $Promises::WARN_ON_UNHANDLED_REJECT ? _trace() : undef ;
22              
23 158         582 bless {
24             _caller => $caller,
25             resolved => [],
26             rejected => [],
27             status => IN_PROGRESS
28             } => $class;
29             }
30              
31             sub _trace {
32 9     9   12 my $i = 0;
33              
34 9         22 while( my( $package, $filename, $line ) = caller($i++) ) {
35 30 100       1281 return [ $filename, $line ] unless $package =~ /^Promises/;
36             }
37              
38             return
39 0         0 }
40              
41 127     127 1 350 sub promise { Promises::Promise->new(shift) }
42 0     0 1 0 sub status { (shift)->{'status'} }
43 255     255 1 515 sub result { (shift)->{'result'} }
44              
45             # predicates for all the status possibilities
46 281     281 1 890 sub is_in_progress { (shift)->{'status'} eq IN_PROGRESS }
47 242     242 1 563 sub is_resolved { (shift)->{'status'} eq RESOLVED }
48 254     254 1 751 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 1972 my $self = shift;
58              
59 133 50       236 die "Cannot resolve. Already " . $self->status
60             unless $self->is_in_progress;
61              
62 133         316 $self->{'result'} = [@_];
63 133         220 $self->{'status'} = RESOLVED;
64 133         374 $self->_notify;
65 133         262 $self;
66             }
67              
68             sub reject {
69 25     25 1 363 my $self = shift;
70 25 50       44 die "Cannot reject. Already " . $self->status
71             unless $self->is_in_progress;
72              
73 25         75 $self->{'result'} = [@_];
74 25         44 $self->{'status'} = REJECTED;
75 25         71 $self->_notify;
76 25         318 $self;
77             }
78              
79             sub then {
80 120     120 1 226 my $self = shift;
81 120         238 my ( $callback, $error ) = $self->_callable_or_undef(@_);
82              
83 120         332 my $d = ( ref $self )->new;
84 120         212 push @{ $self->{'resolved'} } => $self->_wrap( $d, $callback, 'resolve' );
  120         309  
85 120         189 push @{ $self->{'rejected'} } => $self->_wrap( $d, $error, 'reject' );
  120         244  
86              
87 120 100       253 $self->_notify unless $self->is_in_progress;
88 120         250 $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         16 $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 8 my $self = shift;
114 3         7 my ($callback) = $self->_callable_or_undef(@_);
115              
116 3         11 my $d = ( ref $self )->new;
117              
118 3 50       11 if (defined $callback) {
119 3         5 my ( @result, $method );
120 3     3   15 my $finish_d = sub { $d->$method(@result); () };
  3         13  
  3         4  
121              
122             my $f = sub {
123 3     3   9 ( $method, @result ) = @_;
124 3         5 local $@;
125 3         7 my ($p) = eval { $callback->(@result) };
  3         27  
126 3 50 33     353 if ( $p && blessed $p && $p->can('then') ) {
      33        
127 0         0 return $p->then( $finish_d, $finish_d );
128             }
129 3         23 $finish_d->();
130 3         24 ();
131 3         10 };
132              
133 3     2   12 push @{ $self->{'resolved'} } => sub { $f->( 'resolve', @_ ) };
  3         25  
  2         8  
134 3     1   7 push @{ $self->{'rejected'} } => sub { $f->( 'reject', @_ ) };
  3         10  
  1         2  
135              
136 3 100       10 $self->_notify unless $self->is_in_progress;
137             }
138 3         10 $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   486 my ( $self, $d, $f, $method ) = @_;
170              
171 9     9   14 return sub { $d->$method( @{ $self->result } ) }
  9         15  
172 240 100       649 unless defined $f;
173              
174             return sub {
175 111     111   164 local $@;
176 111         163 my ( @results, $error );
177             eval {
178 111         173 @results = do { $f->(@_) };
  111         243  
179 107         7864 1;
180             }
181 111 50       161 || do { $error = defined $@ ? $@ : 'Unknown reason' };
  4 100       708  
182              
183 111 100 100     591 if ($error) {
    100 66        
184 4         18 $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         38 sub { $d->resolve(@_); () },
  13         23  
192 4         13 sub { $d->reject(@_); () },
  4         7  
193 17         91 );
194             }
195             else {
196 90         233 $d->resolve(@results);
197             }
198 111         665 return;
199 154         608 };
200             }
201              
202             sub _notify {
203 242     242   431 my ($self) = @_;
204              
205 242 100       404 my $cbs = $self->is_resolved ? $self->{resolved} : $self->{rejected};
206              
207 242   100     447 $self->{_reject_was_handled} = $self->is_rejected && @$cbs;
208              
209 242         479 $self->{'resolved'} = [];
210 242         552 $self->{'rejected'} = [];
211              
212 242         441 return $self->_notify_backend( $cbs, $self->result );
213             }
214              
215             sub _notify_backend {
216 242     242   405 my ( $self, $cbs, $result ) = @_;
217 242         576 $_->(@$result) foreach @$cbs;
218             }
219              
220             sub _callable_or_undef {
221 123     123   181 shift;
222             map {
223             # coderef or object overloaded as coderef
224 123 100 66     192 ref && reftype $_ eq 'CODE' || blessed $_ && $_->can('()')
  158         877  
225             ? $_
226             : undef
227             } @_;
228             }
229              
230              
231             1;
232              
233             __END__