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   366155 use strict;
  20         42  
  20         746  
3 20     20   92 use warnings;
  20         44  
  20         572  
4 20     20   11825 use Future 0.29;
  20         203572  
  20         630  
5 20     20   9490 use parent "Future";
  20         5735  
  20         96  
6 20     20   10496 use Devel::GlobalDestruction;
  20         37604  
  20         128  
7 20     20   1711 use Scalar::Util qw(refaddr blessed weaken);
  20         37  
  20         1133  
8 20     20   96 use Carp;
  20         29  
  20         1066  
9 20     20   10852 use Try::Tiny ();
  20         24149  
  20         24313  
10              
11             our $VERSION = '0.100';
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 203709 my ($class, @args) = @_;
23 1086         2972 my $self = $class->SUPER::new(@args);
24 1086         7801 my $id = refaddr $self;
25 1086         1952 $failure_handled_for{$id} = 0;
26 1086         2370 return $self;
27             }
28              
29             sub _q_go_super_DESTROY {
30 1071     1071   1157 my ($self) = @_;
31 1071         2536 my $super_destroy = $self->can("SUPER::DESTROY");
32 1071 100       4892 goto $super_destroy if defined $super_destroy;
33             }
34              
35             sub DESTROY {
36 1071     1071   398377 my ($self) = @_;
37 1071 50       27190 if(in_global_destruction) {
38 0         0 goto \&_q_go_super_DESTROY;
39             }
40 1071         5767 my $id = refaddr $self;
41 1071 100 100     2507 if($self->is_ready && $self->failure && !$failure_handled_for{$id}) {
      100        
42 104         1731 $self->_q_warn_failure();
43             my @failed_subfutures = Try::Tiny::try {
44 104     104   2926 $self->failed_futures;
45             }Try::Tiny::catch {
46 98     98   14956 ();
47 104         973 };
48 104         690 foreach my $f (@failed_subfutures) {
49 16 50 33     206 $f->_q_warn_failure(is_subfuture => 1) if blessed($f) && $f->can('_q_warn_failure');
50             }
51             }
52 1071         11743 delete $failure_handled_for{$id};
53 1071         2270 goto \&_q_go_super_DESTROY;
54             }
55              
56             sub _q_set_failure_handled {
57 576     576   579 my ($self) = @_;
58 576         1462 $failure_handled_for{refaddr $self} = 1;
59             }
60              
61             sub _q_warn_failure {
62 120     120   256 my ($self, %options) = @_;
63 120 50 33     260 if($self->is_ready && $self->failure) {
64 120         1415 my $failure = $self->failure;
65 120 100       18073 my $message = Carp::shortmess($options{is_subfuture}
66             ? "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     3185 if(defined($OnError) && ref($OnError) eq "CODE") {
69 117         315 $OnError->($message);
70             }else {
71 3         16 warn $message;
72             }
73             }
74             }
75              
76             sub try {
77 193     193 1 2987 my ($class, $func, @args) = @_;
78 193 100 100     889 if(!defined($func) || ref($func) ne "CODE") {
79             $func = sub {
80 7     7   1110 croak("func parameter for try() must be a code-ref");
81 7         25 };
82             }
83             my $result_future = Try::Tiny::try {
84 193     193   5664 my @results = $func->(@args);
85 163 100 100     32808 if(scalar(@results) == 1 && blessed($results[0]) && $results[0]->isa('Future')) {
      66        
86 96         286 return $results[0];
87             }else {
88 67         145 return $class->new->fulfill(@results);
89             }
90             } Try::Tiny::catch {
91 30     30   9345 my $e = shift;
92 30         75 return $class->new->reject($e);
93 193         1293 };
94 193         4940 return $result_future;
95             }
96              
97             sub fcall {
98 2     2 1 66 goto $_[0]->can('try');
99             }
100              
101             sub then {
102 327     327 1 7009 my ($self, $on_fulfilled, $on_rejected) = @_;
103 327 100 100     1182 if(defined($on_fulfilled) && ref($on_fulfilled) ne "CODE") {
104 4         4 $on_fulfilled = undef;
105             }
106 327 100 100     1036 if(defined($on_rejected) && ref($on_rejected) ne "CODE") {
107 1         1 $on_rejected = undef;
108             }
109 327         416 my $class = ref($self);
110 327         639 $self->_q_set_failure_handled();
111            
112 327         453 my $next_future = $self->new;
113             $self->on_ready(sub {
114 319     319   16204 my $invo_future = shift;
115 319 100       854 if($invo_future->is_cancelled) {
116 98 50       408 $next_future->cancel() if $next_future->is_pending;
117 98         1806 return;
118             }
119 221         848 my $return_future = $invo_future;
120 221 100 100     411 if($invo_future->is_rejected && defined($on_rejected)) {
    100 100        
121 79         1165 $return_future = $class->try($on_rejected, $invo_future->failure);
122             }elsif($invo_future->is_fulfilled && defined($on_fulfilled)) {
123 54         704 $return_future = $class->try($on_fulfilled, $invo_future->get);
124             }
125 221         1473 $next_future->resolve($return_future);
126 327         1506 });
127 327 100 100     3217 if($next_future->is_pending && $self->is_pending) {
128 178         1182 weaken(my $invo_future = $self);
129             $next_future->on_cancel(sub {
130 95 100 66 95   1725 if(defined($invo_future) && $invo_future->is_pending) {
131 12         80 $invo_future->cancel();
132             }
133 178         681 });
134             }
135 327         2971 return $next_future;
136             }
137              
138             sub catch {
139 51     51 1 8910 my ($self, $on_rejected) = @_;
140 51         117 @_ = ($self, undef, $on_rejected);
141 51         166 goto $self->can('then');
142             }
143              
144             sub fulfill {
145 353     353 1 43519 goto $_[0]->can('done');
146             }
147              
148             sub resolve {
149 314     314 1 2497 my ($self, @result) = @_;
150 314 100 100     2731 if(not (@result == 1 && blessed($result[0]) && $result[0]->isa("Future"))) {
      66        
151 7         33 goto $self->can("fulfill");
152             }
153 307 100       729 return $self if $self->is_cancelled;
154 300         1171 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   13450 my $base_future = shift;
165 297 50       610 return if $self->is_ready;
166 297 100       1292 if($base_future->is_cancelled) {
    100          
167 53         256 $self->cancel();
168             }elsif($base_future->failure) {
169 102 100       1113 if($base_future->can("_q_set_failure_handled")) {
170 97         179 $base_future->_q_set_failure_handled();
171             }
172 102         201 $self->reject($base_future->failure);
173             }else {
174 142         1182 $self->fulfill($base_future->get);
175             }
176 300         1371 });
177 300 100       7025 if(!$base_future->is_ready) {
178 76         384 weaken(my $weak_base = $base_future);
179             $self->on_cancel(sub {
180 57 100 66 57   14030 $weak_base->cancel() if defined($weak_base) && !$weak_base->is_ready;
181 76         281 });
182             }
183 300         2025 return $self;
184             }
185              
186             sub reject {
187 257     257 1 38108 goto $_[0]->can('fail');
188             }
189              
190             sub is_pending {
191 1447     1447 1 308122 my ($self) = @_;
192 1447         2621 return !$self->is_ready;
193             }
194              
195             sub is_fulfilled {
196 281     281 1 81480 my ($self) = @_;
197 281   100     489 return (!$self->is_pending && !$self->is_cancelled && !$self->is_rejected);
198             }
199              
200             sub is_rejected {
201 643     643 1 84639 my ($self) = @_;
202 643   100     1206 return ($self->is_ready && !!$self->failure);
203             }
204              
205             foreach my $method (qw(wait_all wait_any needs_all needs_any)) {
206 20     20   131 no strict "refs";
  20         29  
  20         7498  
207             my $supermethod_code = __PACKAGE__->can("SUPER::$method");
208             *{$method} = sub {
209 22     22   418 my ($self, @subfutures) = @_;
210 22         41 foreach my $sub (@subfutures) {
211 99 50 33     565 next if !blessed($sub) || !$sub->can('_q_set_failure_handled');
212 99         220 $sub->_q_set_failure_handled();
213             }
214 22         102 goto $supermethod_code;
215             };
216             }
217              
218             sub finally {
219 53     53 1 890 my ($self, $callback) = @_;
220 53         87 my $class = ref($self);
221 53         98 $self->_q_set_failure_handled();
222 53 50 33     266 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         82 my $next_future = $self->new;
226             $self->on_ready(sub {
227 53     53   13637 my ($invo_future) = @_;
228 53 100       127 if($invo_future->is_cancelled) {
229 17 50       97 $next_future->cancel if $next_future->is_pending;
230 17         323 return;
231             }
232 36         202 my $returned_future = $class->try($callback);
233             $returned_future->on_ready(sub {
234 36         8950 my ($returned_future) = @_;
235 36 100 100     80 if(!$returned_future->is_cancelled && $returned_future->failure) {
236 13         187 $next_future->resolve($returned_future);
237             }else {
238 23         212 $next_future->resolve($invo_future);
239             }
240 36         181 });
241 36 100       339 if(!$returned_future->is_ready) {
242 16         90 weaken(my $weak_returned = $returned_future);
243             $next_future->on_cancel(sub {
244 2 50 33     375 $weak_returned->cancel if defined($weak_returned) && !$weak_returned->is_ready;
245 16         62 });
246             }
247 53         289 });
248 53 100       649 if(!$self->is_ready) {
249 27         152 weaken(my $weak_invo = $self);
250             $next_future->on_cancel(sub {
251 10 100 66 10   324 $weak_invo->cancel if defined($weak_invo) && !$weak_invo->is_ready;
252            
253 27         108 });
254             }
255 53         439 return $next_future;
256             }
257              
258             1;
259              
260             __END__