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   14276160 use strict;
  20         45  
  20         486  
3 20     20   90 use warnings;
  20         42  
  20         567  
4 20     20   921183 use Future 0.29;
  20         275501  
  20         752  
5 20     20   13731 use parent "Future";
  20         5720  
  20         104  
6 20     20   13878 use Devel::GlobalDestruction;
  20         42095  
  20         1328  
7 20     20   1609 use Scalar::Util qw(refaddr blessed weaken);
  20         34  
  20         1053  
8 20     20   1164 use Carp;
  20         39  
  20         1072  
9 20     20   15951 use Try::Tiny ();
  20         28419  
  20         27203  
10              
11             our $VERSION = '0.110';
12              
13             our @CARP_NOT = qw(Try::Tiny Future);
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 284169 my ($class, @args) = @_;
23 1086         3141 my $self = $class->SUPER::new(@args);
24 1086         8759 my $id = refaddr $self;
25 1086         2380 $failure_handled_for{$id} = 0;
26 1086         2890 return $self;
27             }
28              
29             sub _q_go_super_DESTROY {
30 1071     1071   1375 my ($self) = @_;
31 1071         2996 my $super_destroy = $self->can("SUPER::DESTROY");
32 1071 100       9100 goto $super_destroy if defined $super_destroy;
33             }
34              
35             sub DESTROY {
36 1071     1071   587389 my ($self) = @_;
37 1071 50       27643 if(in_global_destruction) {
38 0         0 goto \&_q_go_super_DESTROY;
39             }
40 1071         11699 my $id = refaddr $self;
41 1071 100 100     2857 if($self->is_ready && $self->failure && !$failure_handled_for{$id}) {
      100        
42 104         1857 $self->_q_warn_failure();
43             my @failed_subfutures = Try::Tiny::try {
44 104     104   3024 $self->failed_futures;
45             }Try::Tiny::catch {
46 98     98   16390 ();
47 104         916 };
48 104         631 foreach my $f (@failed_subfutures) {
49 16 50 33     158 $f->_q_warn_failure(is_subfuture => 1) if blessed($f) && $f->can('_q_warn_failure');
50             }
51             }
52 1071         13395 delete $failure_handled_for{$id};
53 1071         2569 goto \&_q_go_super_DESTROY;
54             }
55              
56             sub _q_set_failure_handled {
57 576     576   774 my ($self) = @_;
58 576         1499 $failure_handled_for{refaddr $self} = 1;
59             }
60              
61             sub _q_warn_failure {
62 120     120   254 my ($self, %options) = @_;
63 120 50 33     299 if($self->is_ready && $self->failure) {
64 120         1629 my $failure = $self->failure;
65             my $message = Carp::shortmess($options{is_subfuture}
66 120 100       19293 ? "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     3158 if(defined($OnError) && ref($OnError) eq "CODE") {
69 117         321 $OnError->($message);
70             }else {
71 3         30 warn $message;
72             }
73             }
74             }
75              
76             sub try {
77 193     193 1 4377 my ($class, $func, @args) = @_;
78 193 100 100     923 if(!defined($func) || ref($func) ne "CODE") {
79             $func = sub {
80 7     7   1324 croak("func parameter for try() must be a code-ref");
81 7         23 };
82             }
83             my $result_future = Try::Tiny::try {
84 193     193   5805 my @results = $func->(@args);
85 163 100 100     40071 if(scalar(@results) == 1 && blessed($results[0]) && $results[0]->isa('Future')) {
      66        
86 96         307 return $results[0];
87             }else {
88 67         428 return $class->new->fulfill(@results);
89             }
90             } Try::Tiny::catch {
91 30     30   10560 my $e = shift;
92 30         81 return $class->new->reject($e);
93 193         1776 };
94 193         6988 return $result_future;
95             }
96              
97             sub fcall {
98 2     2 1 165 goto $_[0]->can('try');
99             }
100              
101             sub then {
102 327     327 1 8286 my ($self, $on_fulfilled, $on_rejected) = @_;
103 327 100 100     1249 if(defined($on_fulfilled) && ref($on_fulfilled) ne "CODE") {
104 4         5 $on_fulfilled = undef;
105             }
106 327 100 100     1317 if(defined($on_rejected) && ref($on_rejected) ne "CODE") {
107 1         2 $on_rejected = undef;
108             }
109 327         548 my $class = ref($self);
110 327         716 $self->_q_set_failure_handled();
111            
112 327         614 my $next_future = $self->new;
113             $self->on_ready(sub {
114 319     319   22437 my $invo_future = shift;
115 319 100       892 if($invo_future->is_cancelled) {
116 98 50       518 $next_future->cancel() if $next_future->is_pending;
117 98         2408 return;
118             }
119 221         931 my $return_future = $invo_future;
120 221 100 100     511 if($invo_future->is_rejected && defined($on_rejected)) {
    100 100        
121 79         1821 $return_future = $class->try($on_rejected, $invo_future->failure);
122             }elsif($invo_future->is_fulfilled && defined($on_fulfilled)) {
123 54         822 $return_future = $class->try($on_fulfilled, $invo_future->get);
124             }
125 221         2043 $next_future->resolve($return_future);
126 327         1727 });
127 327 100 100     11494 if($next_future->is_pending && $self->is_pending) {
128 178         1239 weaken(my $invo_future = $self);
129             $next_future->on_cancel(sub {
130 95 100 66 95   2561 if(defined($invo_future) && $invo_future->is_pending) {
131 12         90 $invo_future->cancel();
132             }
133 178         794 });
134             }
135 327         3727 return $next_future;
136             }
137              
138             sub catch {
139 51     51 1 9614 my ($self, $on_rejected) = @_;
140 51         129 @_ = ($self, undef, $on_rejected);
141 51         195 goto $self->can('then');
142             }
143              
144             sub fulfill {
145 353     353 1 73193 goto $_[0]->can('done');
146             }
147              
148             sub resolve {
149 314     314 1 2226 my ($self, @result) = @_;
150 314 100 100     4019 if(not (@result == 1 && blessed($result[0]) && $result[0]->isa("Future"))) {
      66        
151 7         35 goto $self->can("fulfill");
152             }
153 307 100       879 return $self if $self->is_cancelled;
154 300         1268 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   16373 my $base_future = shift;
165 297 50       1006 return if $self->is_ready;
166 297 100       1510 if($base_future->is_cancelled) {
    100          
167 53         285 $self->cancel();
168             }elsif($base_future->failure) {
169 102 100       1361 if($base_future->can("_q_set_failure_handled")) {
170 97         185 $base_future->_q_set_failure_handled();
171             }
172 102         272 $self->reject($base_future->failure);
173             }else {
174 142         1650 $self->fulfill($base_future->get);
175             }
176 300         2127 });
177 300 100       9337 if(!$base_future->is_ready) {
178 76         399 weaken(my $weak_base = $base_future);
179             $self->on_cancel(sub {
180 57 100 66 57   18432 $weak_base->cancel() if defined($weak_base) && !$weak_base->is_ready;
181 76         311 });
182             }
183 300         2350 return $self;
184             }
185              
186             sub reject {
187 257     257 1 65702 goto $_[0]->can('fail');
188             }
189              
190             sub is_pending {
191 1447     1447 1 437122 my ($self) = @_;
192 1447         3563 return !$self->is_ready;
193             }
194              
195             sub is_fulfilled {
196 281     281 1 112567 my ($self) = @_;
197 281   100     581 return (!$self->is_pending && !$self->is_cancelled && !$self->is_rejected);
198             }
199              
200             sub is_rejected {
201 643     643 1 131734 my ($self) = @_;
202 643   100     1708 return ($self->is_ready && !!$self->failure);
203             }
204              
205             foreach my $method (qw(wait_all wait_any needs_all needs_any)) {
206 20     20   1295 no strict "refs";
  20         38  
  20         10024  
207             my $supermethod_code = __PACKAGE__->can("SUPER::$method");
208             *{$method} = sub {
209 22     22   348 my ($self, @subfutures) = @_;
210 22         40 foreach my $sub (@subfutures) {
211 99 50 33     509 next if !blessed($sub) || !$sub->can('_q_set_failure_handled');
212 99         193 $sub->_q_set_failure_handled();
213             }
214 22         79 goto $supermethod_code;
215             };
216             }
217              
218             sub finally {
219 53     53 1 1091 my ($self, $callback) = @_;
220 53         92 my $class = ref($self);
221 53         124 $self->_q_set_failure_handled();
222 53 50 33     318 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         125 my $next_future = $self->new;
226             $self->on_ready(sub {
227 53     53   20920 my ($invo_future) = @_;
228 53 100       152 if($invo_future->is_cancelled) {
229 17 50       123 $next_future->cancel if $next_future->is_pending;
230 17         426 return;
231             }
232 36         444 my $returned_future = $class->try($callback);
233             $returned_future->on_ready(sub {
234 36         11867 my ($returned_future) = @_;
235 36 100 100     102 if(!$returned_future->is_cancelled && $returned_future->failure) {
236 13         197 $next_future->resolve($returned_future);
237             }else {
238 23         255 $next_future->resolve($invo_future);
239             }
240 36         207 });
241 36 100       609 if(!$returned_future->is_ready) {
242 16         117 weaken(my $weak_returned = $returned_future);
243             $next_future->on_cancel(sub {
244 2 50 33     1069 $weak_returned->cancel if defined($weak_returned) && !$weak_returned->is_ready;
245 16         74 });
246             }
247 53         340 });
248 53 100       941 if(!$self->is_ready) {
249 27         174 weaken(my $weak_invo = $self);
250             $next_future->on_cancel(sub {
251 10 100 66 10   575 $weak_invo->cancel if defined($weak_invo) && !$weak_invo->is_ready;
252            
253 27         140 });
254             }
255 53         571 return $next_future;
256             }
257              
258             1;
259              
260             __END__