File Coverage

blib/lib/Future/Q.pm
Criterion Covered Total %
statement 153 155 98.7
branch 59 68 86.7
condition 56 72 77.7
subroutine 37 37 100.0
pod 12 12 100.0
total 317 344 92.1


line stmt bran cond sub pod time code
1             package Future::Q;
2 20     20   1280098 use strict;
  20         192  
  20         500  
3 20     20   92 use warnings;
  20         35  
  20         489  
4 20     20   8962 use Future 0.29;
  20         262834  
  20         591  
5 20     20   7054 use parent "Future";
  20         5003  
  20         94  
6 20     20   7817 use Devel::GlobalDestruction;
  20         33980  
  20         98  
7 20     20   1228 use Scalar::Util qw(refaddr blessed weaken);
  20         37  
  20         822  
8 20     20   94 use Carp;
  20         33  
  20         834  
9 20     20   10747 use Try::Tiny ();
  20         33941  
  20         24807  
10              
11             our $VERSION = '0.120';
12              
13             our @CARP_NOT = qw(Try::Tiny Future Future::PP Future::XS);
14              
15             our $OnError = undef;
16              
17             ## ** lexical attributes to avoid collision of names.
18              
19             my %failure_handled_for = ();
20              
21             sub new {
22 1086     1086 1 379989 my ($class, @args) = @_;
23 1086         2663 my $self = $class->SUPER::new(@args);
24 1086         6625 my $id = refaddr $self;
25 1086         1989 $failure_handled_for{$id} = 0;
26 1086         2179 return $self;
27             }
28              
29             sub _q_go_super_DESTROY {
30 1071     1071   1542 my ($self) = @_;
31 1071         2386 my $super_destroy = $self->can("SUPER::DESTROY");
32 1071 100       3898 goto $super_destroy if defined $super_destroy;
33             }
34              
35             sub DESTROY {
36 1071     1071   438791 my ($self) = @_;
37 1071 50       23451 if(in_global_destruction) {
38 0         0 goto \&_q_go_super_DESTROY;
39             }
40 1071         5929 my $id = refaddr $self;
41 1071 100 100     2100 if($self->is_ready && $self->failure && !$failure_handled_for{$id}) {
      100        
42 104         1675 $self->_q_warn_failure();
43             my @failed_subfutures = Try::Tiny::try {
44 104     104   3852 $self->failed_futures;
45             }Try::Tiny::catch {
46 98     98   13693 ();
47 104         815 };
48 104         654 foreach my $f (@failed_subfutures) {
49 16 50 33     122 $f->_q_warn_failure(is_subfuture => 1) if blessed($f) && $f->can('_q_warn_failure');
50             }
51             }
52 1071         10594 delete $failure_handled_for{$id};
53 1071         2443 goto \&_q_go_super_DESTROY;
54             }
55              
56             sub _q_set_failure_handled {
57 576     576   766 my ($self) = @_;
58 576         1260 $failure_handled_for{refaddr $self} = 1;
59             }
60              
61             sub _q_warn_failure {
62 120     120   252 my ($self, %options) = @_;
63 120 50 33     211 if($self->is_ready && $self->failure) {
64 120         1407 my $failure = $self->failure;
65             my $message = Carp::shortmess($options{is_subfuture}
66 120 100       16468 ? "Failure of subfuture $self may not be handled: $failure subfuture may be lost"
67             : "Failure of $self is not handled: $failure future is lost");
68 120 100 66     2806 if(defined($OnError) && ref($OnError) eq "CODE") {
69 117         292 $OnError->($message);
70             }else {
71 3         21 warn $message;
72             }
73             }
74             }
75              
76             sub try {
77 193     193 1 9976 my ($class, $func, @args) = @_;
78 193 100 100     728 if(!defined($func) || ref($func) ne "CODE") {
79             $func = sub {
80 7     7   1146 croak("func parameter for try() must be a code-ref");
81 7         25 };
82             }
83             my $result_future = Try::Tiny::try {
84 193     193   7199 my @results = $func->(@args);
85 163 100 100     37981 if(scalar(@results) == 1 && blessed($results[0]) && $results[0]->isa('Future')) {
      66        
86 96         345 return $results[0];
87             }else {
88 67         168 return $class->new->fulfill(@results);
89             }
90             } Try::Tiny::catch {
91 30     30   8152 my $e = shift;
92 30         68 return $class->new->reject($e);
93 193         1047 };
94 193         5734 return $result_future;
95             }
96              
97             sub fcall {
98 2     2 1 431 goto $_[0]->can('try');
99             }
100              
101             sub then {
102 327     327 1 7122 my ($self, $on_fulfilled, $on_rejected) = @_;
103 327 100 100     1001 if(defined($on_fulfilled) && ref($on_fulfilled) ne "CODE") {
104 4         5 $on_fulfilled = undef;
105             }
106 327 100 100     906 if(defined($on_rejected) && ref($on_rejected) ne "CODE") {
107 1         2 $on_rejected = undef;
108             }
109 327         462 my $class = ref($self);
110 327         693 $self->_q_set_failure_handled();
111            
112 327         476 my $next_future = $self->new;
113             $self->on_ready(sub {
114 319     319   17726 my $invo_future = shift;
115 319 100       617 if($invo_future->is_cancelled) {
116 98 50       428 $next_future->cancel() if $next_future->is_pending;
117 98         2374 return;
118             }
119 221         904 my $return_future = $invo_future;
120 221 100 100     385 if($invo_future->is_rejected && defined($on_rejected)) {
    100 100        
121 79         1074 $return_future = $class->try($on_rejected, $invo_future->failure);
122             }elsif($invo_future->is_fulfilled && defined($on_fulfilled)) {
123 54         649 $return_future = $class->try($on_fulfilled, $invo_future->get);
124             }
125 221         1425 $next_future->resolve($return_future);
126 327         1494 });
127 327 100 100     4484 if($next_future->is_pending && $self->is_pending) {
128 178         1067 weaken(my $invo_future = $self);
129             $next_future->on_cancel(sub {
130 79 100 66 79   2979 if(defined($invo_future) && $invo_future->is_pending) {
131 12         75 $invo_future->cancel();
132             }
133 178         619 });
134             }
135 327         3744 return $next_future;
136             }
137              
138             sub catch {
139 51     51 1 8571 my ($self, $on_rejected) = @_;
140 51         103 @_ = ($self, undef, $on_rejected);
141 51         178 goto $self->can('then');
142             }
143              
144             sub fulfill {
145 353     353 1 52883 goto $_[0]->can('done');
146             }
147              
148             sub resolve {
149 314     314 1 2675 my ($self, @result) = @_;
150 314 100 100     2324 if(not (@result == 1 && blessed($result[0]) && $result[0]->isa("Future"))) {
      66        
151 7         33 goto $self->can("fulfill");
152             }
153 307 100       733 return $self if $self->is_cancelled;
154 300         1230 my $base_future = $result[0];
155              
156             ## Maybe we should check if $base_future is identical to
157             ## $self. Promises/A+ spec v1.1 [1] states we should reject $self
158             ## in that case. However, since Q v1.0.1 does not care that case,
159             ## we also leave that case unchecked for now.
160             ##
161             ## [1]: https://github.com/promises-aplus/promises-spec/tree/1.1.0
162            
163             $base_future->on_ready(sub {
164 297     297   14619 my $base_future = shift;
165 297 50       489 return if $self->is_ready;
166 297 100       1281 if($base_future->is_cancelled) {
    100          
167 53         258 $self->cancel();
168             }elsif($base_future->failure) {
169 102 100       1297 if($base_future->can("_q_set_failure_handled")) {
170 97         164 $base_future->_q_set_failure_handled();
171             }
172 102         193 $self->reject($base_future->failure);
173             }else {
174 142         1277 $self->fulfill($base_future->get);
175             }
176 300         1409 });
177 300 100       8860 if(!$base_future->is_ready) {
178 76         383 weaken(my $weak_base = $base_future);
179             $self->on_cancel(sub {
180 43 100 66 43   20533 $weak_base->cancel() if defined($weak_base) && !$weak_base->is_ready;
181 76         289 });
182             }
183 300         2247 return $self;
184             }
185              
186             sub reject {
187 257     257 1 38061 goto $_[0]->can('fail');
188             }
189              
190             sub is_pending {
191 1431     1431 1 366253 my ($self) = @_;
192 1431         2544 return !$self->is_ready;
193             }
194              
195             sub is_fulfilled {
196 281     281 1 98643 my ($self) = @_;
197 281   100     457 return (!$self->is_pending && !$self->is_cancelled && !$self->is_rejected);
198             }
199              
200             sub is_rejected {
201 643     643 1 95956 my ($self) = @_;
202 643   100     1097 return ($self->is_ready && !!$self->failure);
203             }
204              
205             foreach my $method (qw(wait_all wait_any needs_all needs_any)) {
206 20     20   150 no strict "refs";
  20         29  
  20         8273  
207             my $supermethod_code = __PACKAGE__->can("SUPER::$method");
208             *{$method} = sub {
209 22     22   339 my ($self, @subfutures) = @_;
210 22         36 foreach my $sub (@subfutures) {
211 99 50 33     396 next if !blessed($sub) || !$sub->can('_q_set_failure_handled');
212 99         161 $sub->_q_set_failure_handled();
213             }
214 22         70 goto $supermethod_code;
215             };
216             }
217              
218             sub finally {
219 53     53 1 1119 my ($self, $callback) = @_;
220 53         82 my $class = ref($self);
221 53         126 $self->_q_set_failure_handled();
222 53 50 33     232 if(!defined($callback) || ref($callback) ne "CODE") {
223 0         0 return $class->new->reject("Callback for finally() must be a code-ref");
224             }
225 53         92 my $next_future = $self->new;
226             $self->on_ready(sub {
227 53     53   16138 my ($invo_future) = @_;
228 53 100       109 if($invo_future->is_cancelled) {
229 17 50       97 $next_future->cancel if $next_future->is_pending;
230 17         449 return;
231             }
232 36         187 my $returned_future = $class->try($callback);
233             $returned_future->on_ready(sub {
234 36         8613 my ($returned_future) = @_;
235 36 100 100     78 if(!$returned_future->is_cancelled && $returned_future->failure) {
236 13         190 $next_future->resolve($returned_future);
237             }else {
238 23         208 $next_future->resolve($invo_future);
239             }
240 36         179 });
241 36 100       419 if(!$returned_future->is_ready) {
242 16         104 weaken(my $weak_returned = $returned_future);
243             $next_future->on_cancel(sub {
244 2 50 33     440 $weak_returned->cancel if defined($weak_returned) && !$weak_returned->is_ready;
245 16         56 });
246             }
247 53         253 });
248 53 100       873 if(!$self->is_ready) {
249 27         178 weaken(my $weak_invo = $self);
250             $next_future->on_cancel(sub {
251 9 100 66 9   371 $weak_invo->cancel if defined($weak_invo) && !$weak_invo->is_ready;
252            
253 27         126 });
254             }
255 53         590 return $next_future;
256             }
257              
258             1;
259              
260             __END__