File Coverage

blib/lib/Promises/Deferred.pm
Criterion Covered Total %
statement 130 146 89.0
branch 32 42 76.1
condition 16 18 88.8
subroutine 36 42 85.7
pod 19 19 100.0
total 233 267 87.2


line stmt bran cond sub pod time code
1             package Promises::Deferred;
2             our $AUTHORITY = 'cpan:YANICK';
3             $Promises::Deferred::VERSION = '1.05';
4             # ABSTRACT: An implementation of Promises in Perl
5              
6 35     35   534922 use strict;
  35         81  
  35         1362  
7 35     35   218 use warnings;
  35         60  
  35         2065  
8              
9 35     35   215 use Scalar::Util qw[ blessed reftype ];
  35         74  
  35         2716  
10 35     35   230 use Carp qw[ confess carp ];
  35         80  
  35         2515  
11              
12 35     35   22182 use Promises::Promise;
  35         135  
  35         1705  
13              
14 35     35   248 use constant IN_PROGRESS => 'in progress';
  35         71  
  35         7741  
15 35     35   279 use constant RESOLVED => 'resolved';
  35         88  
  35         2092  
16 35     35   244 use constant REJECTED => 'rejected';
  35         66  
  35         72688  
17              
18             sub new {
19 602     602 1 856000 my $class = shift;
20              
21 602 100       1424 my $caller = $Promises::WARN_ON_UNHANDLED_REJECT ? _trace() : undef ;
22              
23 602         3657 bless {
24             _caller => $caller,
25             resolved => [],
26             rejected => [],
27             status => IN_PROGRESS
28             } => $class;
29             }
30              
31             sub _trace {
32 9     9   16 my $i = 0;
33              
34 9         32 while( my( $package, $filename, $line ) = caller($i++) ) {
35 30 100       1498 return [ $filename, $line ] unless $package =~ /^Promises/;
36             }
37              
38             return
39 0         0 }
40              
41 549     549 1 4652 sub promise { Promises::Promise->new(shift) }
42 43     43 1 405 sub status { (shift)->{'status'} }
43 754     754 1 2283 sub result { (shift)->{'result'} }
44              
45             # predicates for all the status possibilities
46 1085     1085 1 3641 sub is_in_progress { (shift)->{'status'} eq IN_PROGRESS }
47 718     718 1 2076 sub is_resolved { (shift)->{'status'} eq RESOLVED }
48 729     729 1 3592 sub is_rejected { (shift)->{'status'} eq REJECTED }
49 1     1 1 31 sub is_done { ! $_[0]->is_in_progress }
50              
51             # the three possible states according to the spec ...
52 1     1 1 5 sub is_unfulfilled { (shift)->is_in_progress }
53 1     1 1 5 sub is_fulfilled { $_[0]->is_resolved }
54 0     0 1 0 sub is_failed { $_[0]->is_rejected }
55              
56             sub resolve {
57 487     487 1 4095579 my $self = shift;
58              
59 487 50       1167 confess "Cannot resolve. Already " . $self->status
60             unless $self->is_in_progress;
61              
62 487         2598 $self->{'result'} = [@_];
63 487         954 $self->{'status'} = RESOLVED;
64 487         1595 $self->_notify;
65 484         1942 $self;
66             }
67              
68             sub reject {
69 107     107 1 1590487 my $self = shift;
70 107 50       316 confess "Cannot reject. Already " . $self->status
71             unless $self->is_in_progress;
72              
73 107         402 $self->{'result'} = [@_];
74 107         254 $self->{'status'} = REJECTED;
75 107         587 $self->_notify;
76 104         896 $self;
77             }
78              
79             sub then {
80 409     409 1 830 my $self = shift;
81 409         1083 my ( $callback, $error ) = $self->_callable_or_undef(@_);
82              
83 409         3667 my $d = ( ref $self )->new;
84 409         913 push @{ $self->{'resolved'} } => $self->_wrap( $d, $callback, 'resolve' );
  409         1309  
85 409         700 push @{ $self->{'rejected'} } => $self->_wrap( $d, $error, 'reject' );
  409         1043  
86              
87 409 100       1133 $self->_notify unless $self->is_in_progress;
88 409         963 $d->promise;
89             }
90              
91             sub chain {
92 1     1 1 3 my $self = shift;
93 1         4 $self = $self->then($_) for @_;
94 1         7 return $self;
95             }
96              
97             sub catch {
98 24     24 1 62 my $self = shift;
99 24         93 $self->then( undef, @_ );
100             }
101              
102             sub done {
103 58     58 1 143 my $self = shift;
104 58         147 my ( $callback, $error ) = $self->_callable_or_undef(@_);
105 58 100       163 push @{ $self->{'resolved'} } => $callback if defined $callback;
  57         144  
106 58 100       157 push @{ $self->{'rejected'} } => $error if defined $error;
  57         134  
107              
108 58 100       134 $self->_notify unless $self->is_in_progress;
109 58         486 ();
110             }
111              
112             sub finally {
113 21     21 1 40 my $self = shift;
114 21         80 my ($callback) = $self->_callable_or_undef(@_);
115              
116 21         75 my $d = ( ref $self )->new;
117              
118 21 50       77 if (defined $callback) {
119 21         41 my ( @result, $method );
120 21     21   83 my $finish_d = sub { $d->$method(@result); () };
  21         106  
  21         65  
121              
122             my $f = sub {
123 21     21   111 ( $method, @result ) = @_;
124 21         40 local $@;
125 21         84 my ($p) = eval { $callback->(@result) };
  21         83  
126 21 100 100     1183 if ( $p && blessed $p && $p->can('then') ) {
      66        
127 8         25 return $p->then( $finish_d, $finish_d );
128             }
129 13         45 $finish_d->();
130 13         159 ();
131 21         83 };
132              
133 21     11   40 push @{ $self->{'resolved'} } => sub { $f->( 'resolve', @_ ) };
  21         84  
  11         41  
134 21     10   76 push @{ $self->{'rejected'} } => sub { $f->( 'reject', @_ ) };
  21         91  
  10         39  
135              
136 21 100       59 $self->_notify unless $self->is_in_progress;
137             }
138 21         63 $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 818     818   1748 my ( $self, $d, $f, $method ) = @_;
170              
171 26     26   51 return sub { $d->$method( @{ $self->result } ) }
  26         64  
172 818 100       2560 unless defined $f;
173              
174             return sub {
175 378     378   571 local $@;
176 378         622 my ( @results, $error );
177             eval {
178 378         558 @results = do { $f->(@_) };
  378         1233  
179 348         19133 1;
180             }
181 378 50       705 || do { $error = defined $@ ? $@ : 'Unknown reason' };
  30 100       1383  
182              
183 378 100 100     2835 if ($error) {
    100 66        
184 30         124 $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 24         96 sub { $d->resolve(@_); () },
  24         91  
192 9         73 sub { $d->reject(@_); () },
  9         20  
193 33         259 );
194             }
195             else {
196 315         918 $d->resolve(@results);
197             }
198 373         5275 return;
199 517         2655 };
200             }
201              
202             sub _notify {
203 716     716   1460 my ($self) = @_;
204              
205 716 100       1586 my $cbs = $self->is_resolved ? $self->{resolved} : $self->{rejected};
206              
207 716   100     1692 $self->{_reject_was_handled} = $self->is_rejected && @$cbs;
208              
209 716         1959 $self->{'resolved'} = [];
210 716         2746 $self->{'rejected'} = [];
211              
212 716         1661 return $self->_notify_backend( $cbs, $self->result );
213             }
214              
215             sub _notify_backend {
216 604     604   1281 my ( $self, $cbs, $result ) = @_;
217 604         1827 $_->(@$result) foreach @$cbs;
218             }
219              
220             sub _callable_or_undef {
221 488     488   792 shift;
222             map {
223             # coderef or object overloaded as coderef
224 488 100 100     989 ref && reftype $_ eq 'CODE' || blessed $_ && $_->can('()')
  678         3967  
225             ? $_
226             : undef
227             } @_;
228             }
229              
230              
231             1;
232              
233             __END__