File Coverage

blib/lib/Promise/ES6/Backend/PP.pm
Criterion Covered Total %
statement 159 164 96.9
branch 56 68 82.3
condition 15 27 55.5
subroutine 28 30 93.3
pod 0 11 0.0
total 258 300 86.0


line stmt bran cond sub pod time code
1             package Promise::ES6;
2              
3             #----------------------------------------------------------------------
4             # This module iS NOT a defined interface. Nothing to see here …
5             #----------------------------------------------------------------------
6              
7 42     42   288 use strict;
  42         91  
  42         1189  
8 42     42   214 use warnings;
  42         92  
  42         955  
9              
10 42     42   258 use Carp ();
  42         89  
  42         1502  
11              
12             use constant {
13              
14             # These aren’t actually defined.
15 42         7140 _RESOLUTION_CLASS => 'Promise::ES6::_RESOLUTION',
16             _REJECTION_CLASS => 'Promise::ES6::_REJECTION',
17             _PENDING_CLASS => 'Promise::ES6::_PENDING',
18              
19             _DEBUG => 0,
20 42     42   267 };
  42         84  
21              
22             use constant {
23 42         85343 _PROMISE_ID_IDX => 0,
24             _PID_IDX => _DEBUG + 0,
25             _CHILDREN_IDX => _DEBUG + 1,
26             _VALUE_SR_IDX => _DEBUG + 2,
27             _DETECT_LEAK_IDX => _DEBUG + 3,
28             _ON_RESOLVE_IDX => _DEBUG + 4,
29             _ON_REJECT_IDX => _DEBUG + 5,
30             _IS_FINALLY_IDX => _DEBUG + 6,
31              
32             # For async/await:
33             _ON_READY_IMMEDIATE_IDX => _DEBUG + 7,
34             _SELF_REF_IDX => _DEBUG + 8,
35 42     42   361 };
  42         103  
36              
37             # "$value_sr" => $value_sr
38             our %_UNHANDLED_REJECTIONS;
39              
40             my $_debug_promise_id = 0;
41 0     0   0 sub _create_promise_id { return $_debug_promise_id++ . "-$_[0]" }
42              
43             sub new {
44 139     139 0 88245 my ( $class, $cr ) = @_;
45              
46 139 50       398 die 'Need callback!' if !$cr;
47              
48 139         218 my $value;
49 139         482 my $value_sr = bless \$value, _PENDING_CLASS();
50              
51 139         242 my @children;
52              
53 139         573 my $self = bless [
54             ( _DEBUG ? undef : () ),
55             $$,
56             \@children,
57             $value_sr,
58             $Promise::ES6::DETECT_MEMORY_LEAKS,
59             ], $class;
60              
61 139         225 $self->[_PROMISE_ID_IDX] = _create_promise_id($self) if _DEBUG;
62              
63             # NB: These MUST NOT refer to $self, or else we can get memory leaks
64             # depending on how $resolver and $rejector are used.
65             my $resolver = sub {
66 59     59   1078099 $$value_sr = $_[0];
67              
68             # NB: UNIVERSAL::can() is used in order to avoid an eval {}.
69             # It is acknowledged that many Perl experts strongly discourage
70             # use of this technique.
71 59 100       681 if ( UNIVERSAL::can( $$value_sr, 'then' ) ) {
72 2         9 return _repromise( $value_sr, \@children, $value_sr );
73             }
74              
75 57         455 bless $value_sr, _RESOLUTION_CLASS();
76              
77 57 50       283 $self->[_ON_READY_IMMEDIATE_IDX]->() if $self->[_ON_READY_IMMEDIATE_IDX];
78              
79 57         167 undef $self->[_SELF_REF_IDX];
80              
81 57 100       222 if (@children) {
82 17         220 $_->_settle($value_sr) for splice @children;
83             }
84 139         634 };
85              
86             my $rejecter = sub {
87 44 100   44   112382 if (!defined $_[0]) {
88 4         7 my $msg;
89              
90 4 100       13 if (@_) {
91 3         8 $msg = "$class: Uninitialized rejection value given";
92             }
93             else {
94 1         3 $msg = "$class: No rejection value given";
95             }
96              
97 4         526 Carp::carp($msg);
98             }
99              
100 44         127 $$value_sr = $_[0];
101 44         239 bless $value_sr, _REJECTION_CLASS();
102              
103 44         178 $_UNHANDLED_REJECTIONS{$value_sr} = $value_sr;
104              
105 44 50       148 $self->[_ON_READY_IMMEDIATE_IDX]->() if $self->[_ON_READY_IMMEDIATE_IDX];
106              
107 44         119 undef $self->[_SELF_REF_IDX];
108              
109             # We do not repromise rejections. Whatever is in $$value_sr
110             # is literally what rejection callbacks receive.
111 44 100       145 if (@children) {
112 8         100 $_->_settle($value_sr) for splice @children;
113             }
114 139         632 };
115              
116 139         279 local $@;
117 139 100       309 if ( !eval { $cr->( $resolver, $rejecter ); 1 } ) {
  139         433  
  130         691  
118 9         88 $$value_sr = $@;
119 9         29 bless $value_sr, _REJECTION_CLASS();
120              
121 9         28 $_UNHANDLED_REJECTIONS{$value_sr} = $value_sr;
122             }
123              
124 139         1448 return $self;
125             }
126              
127             sub then {
128 130     130 0 16977 return $_[0]->_then_or_finally(@_[1, 2]);
129             }
130              
131             sub finally {
132              
133             # There’s no reason to call finally() without a callback
134             # since it would just be a no-op.
135 13 50   13 0 93 die 'finally() requires a callback!' if !$_[1];
136              
137 13         36 return $_[0]->_then_or_finally($_[1], undef, 1);
138             }
139              
140             sub _then_or_finally {
141 143     143   409 my ($self, $on_resolve_or_finish, $on_reject, $is_finally) = @_;
142              
143 143         236 my $value_sr = bless( \do { my $v }, _PENDING_CLASS() );
  143         431  
144              
145 143         730 my $new = bless [
146             ( _DEBUG ? undef : () ),
147             $$,
148             [],
149             $value_sr,
150             $Promise::ES6::DETECT_MEMORY_LEAKS,
151             $on_resolve_or_finish,
152             $on_reject,
153             $is_finally,
154             ],
155             ref($self);
156              
157 143         265 $new->[_PROMISE_ID_IDX] = _create_promise_id($new) if _DEBUG;
158              
159 143 100       604 if ( _PENDING_CLASS eq ref $self->[_VALUE_SR_IDX] ) {
160 46         143 push @{ $self->[_CHILDREN_IDX] }, $new;
  46         158  
161             }
162             else {
163              
164             # $self might already be settled, in which case we immediately
165             # settle the $new promise as well.
166              
167 97         231 $new->_settle( $self->[_VALUE_SR_IDX] );
168             }
169              
170 143         541 return $new;
171             }
172              
173             sub _repromise {
174 10     10   40 my ( $value_sr, $children_ar, $repromise_value_sr, $orig_finally_sr ) = @_;
175             $$repromise_value_sr->then(
176             sub {
177 6 100   6   25 if (ref $orig_finally_sr) {
178 1         3 $$value_sr = $$orig_finally_sr;
179             }
180             else {
181 5         16 $$value_sr = $_[0];
182             }
183              
184 6         18 bless $value_sr, _RESOLUTION_CLASS;
185 6         107 $_->_settle($value_sr) for splice @$children_ar;
186             },
187             sub {
188 4     4   10 $$value_sr = $_[0];
189 4         8 bless $value_sr, _REJECTION_CLASS;
190 4         10 $_UNHANDLED_REJECTIONS{$value_sr} = $value_sr;
191 4         16 $_->_settle($value_sr) for splice @$children_ar;
192             },
193 10         163 );
194 10         70 return;
195              
196             }
197              
198             # It’s gainfully faster to inline this:
199             #sub _is_completed {
200             # return (_PENDING_CLASS ne ref $_[0][ _VALUE_SR_IDX ]);
201             #}
202              
203             # This method *only* runs to “settle” a promise.
204             sub _settle {
205 133     133   316 my ( $self, $final_value_sr ) = @_;
206              
207 133 50       643 die "$self already settled!" if _PENDING_CLASS ne ref $self->[_VALUE_SR_IDX];
208              
209 133         364 my $settle_is_rejection = _REJECTION_CLASS eq ref $final_value_sr;
210              
211             # This has to happen up-front or else we can get spurious
212             # unhandled-rejection warnings in asynchronous mode.
213 133 100       431 delete $_UNHANDLED_REJECTIONS{$final_value_sr} if $settle_is_rejection;
214              
215 133 50       347 if ($Promise::ES6::_EVENT) {
216             _postpone( sub {
217 0     0   0 $self->_settle_now($final_value_sr, $settle_is_rejection);
218 0         0 } );
219             }
220             else {
221 133         612 $self->_settle_now($final_value_sr, $settle_is_rejection);
222             }
223             }
224              
225             sub _settle_now {
226 139     139   480 my ( $self, $final_value_sr, $settle_is_rejection ) = @_;
227              
228 139         268 my $self_is_finally = $self->[_IS_FINALLY_IDX];
229              
230             # A promise that new() created won’t have on-settle callbacks,
231             # but a promise that came from then/catch/finally will.
232             # It’s a good idea to delete the callbacks in order to trigger garbage
233             # collection as soon and as reliably as possible. It’s safe to do so
234             # because _settle() is only called once.
235 139 100 100     639 my $callback = $self->[ ($settle_is_rejection && !$self_is_finally) ? _ON_REJECT_IDX : _ON_RESOLVE_IDX ];
236              
237 139         255 @{$self}[ _ON_RESOLVE_IDX, _ON_REJECT_IDX ] = ();
  139         400  
238              
239             # In some contexts this function runs quite a lot,
240             # so caching the is-promise lookup is useful.
241 139         271 my $value_sr_contents_is_promise = 1;
242              
243 139 100       319 if ($callback) {
244              
245             # This is the block that runs for promises that were created by a
246             # call to then() that assigned a handler for the state that
247             # $final_value_sr indicates (i.e., resolved or rejected).
248              
249 130         210 my ($new_value, $callback_failed);
250              
251 130         222 local $@;
252              
253 130 100       236 if ( eval { $new_value = $callback->($self_is_finally ? () : $$final_value_sr); 1 } ) {
  130 100       487  
  127         7788  
254              
255             # The callback succeeded. If $new_value is not itself a promise,
256             # then $self is now resolved. (Yay!) Note that this is true
257             # even if $final_value_sr indicates a rejection: in this case, we’ve
258             # just run a successful “catch” block, so resolution is correct.
259              
260             # If $new_value IS a promise, though, then we have to wait.
261 127 100       557 if ( !UNIVERSAL::can( $new_value, 'then' ) ) {
262 119         309 $value_sr_contents_is_promise = 0;
263              
264 119 100       295 if ($self_is_finally) {
265              
266             # finally() is a bit weird. Assuming its callback succeeds,
267             # it takes its parent’s resolution state. It’s important
268             # that we make a *new* reference to the resolution value,
269             # though, rather than merely using $final_value_sr itself,
270             # because we need $self to have its own entry in
271             # %_UNHANDLED_REJECTIONS.
272 8         11 ${ $self->[_VALUE_SR_IDX] } = $$final_value_sr;
  8         16  
273 8         18 bless $self->[_VALUE_SR_IDX], ref $final_value_sr;
274              
275 8 100       26 $_UNHANDLED_REJECTIONS{ $self->[_VALUE_SR_IDX] } = $self->[_VALUE_SR_IDX] if $settle_is_rejection;
276             }
277             else {
278 111         300 bless $self->[_VALUE_SR_IDX], _RESOLUTION_CLASS;
279             }
280             }
281             }
282             else {
283 3         29 $callback_failed = 1;
284              
285             # The callback errored, which means $self is now rejected.
286              
287 3         6 $new_value = $@;
288 3         8 $value_sr_contents_is_promise = 0;
289              
290 3         8 bless $self->[_VALUE_SR_IDX], _REJECTION_CLASS();
291 3         10 $_UNHANDLED_REJECTIONS{ $self->[_VALUE_SR_IDX] } = $self->[_VALUE_SR_IDX];
292             }
293              
294 130 100 100     459 if (!$self_is_finally || $value_sr_contents_is_promise || ($self_is_finally && $callback_failed)) {
      66        
      100        
295 122         189 ${ $self->[_VALUE_SR_IDX] } = $new_value;
  122         357  
296             }
297             }
298             else {
299              
300             # There was no handler from then(), so whatever state $final_value_sr
301             # indicates # (i.e., resolution or rejection) is now $self’s state
302             # as well.
303              
304             # NB: We should NEVER be here if the promise is from finally().
305              
306 9         24 bless $self->[_VALUE_SR_IDX], ref($final_value_sr);
307 9         19 ${ $self->[_VALUE_SR_IDX] } = $$final_value_sr;
  9         21  
308 9         52 $value_sr_contents_is_promise = UNIVERSAL::can( $$final_value_sr, 'then' );
309              
310 9 100       27 if ($settle_is_rejection) {
311 4         16 $_UNHANDLED_REJECTIONS{ $self->[_VALUE_SR_IDX] } = $self->[_VALUE_SR_IDX];
312             }
313             }
314              
315 139 100       390 if ($value_sr_contents_is_promise) {
    100          
316              
317             # Stash the given concrete value. If the $value_sr promise
318             # rejects, then we’ll accept that, but if it resolves, then
319             # we’ll look at this to know to discard that resolution.
320 8 100       43 if ($self_is_finally) {
321 3         7 $self->[_IS_FINALLY_IDX] = $final_value_sr;
322             }
323              
324 8         24 return _repromise( @{$self}[ _VALUE_SR_IDX, _CHILDREN_IDX, _VALUE_SR_IDX, _IS_FINALLY_IDX ] );
  8         34  
325             }
326 131         350 elsif ( @{ $self->[_CHILDREN_IDX] } ) {
327 2         5 $_->_settle( $self->[_VALUE_SR_IDX] ) for splice @{ $self->[_CHILDREN_IDX] };
  2         40  
328             }
329              
330 131 100       321 $self->[_ON_READY_IMMEDIATE_IDX]->() if $self->[_ON_READY_IMMEDIATE_IDX];
331              
332 131         271 undef $self->[_SELF_REF_IDX];
333              
334 131         484 return;
335             }
336              
337             sub DESTROY {
338              
339             # The PID should always be there, but this accommodates mocks.
340 10 50 33 10   5013 return unless $_[0][_PID_IDX] && $$ == $_[0][_PID_IDX];
341              
342 10 0 33     31 if ( $_[0][_DETECT_LEAK_IDX] && ${^GLOBAL_PHASE} && ${^GLOBAL_PHASE} eq 'DESTRUCT' ) {
      0        
343 0         0 warn( ( '=' x 70 ) . "\n" . 'XXXXXX - ' . ref( $_[0] ) . " survived until global destruction; memory leak likely!\n" . ( "=" x 70 ) . "\n" );
344             }
345              
346 10 50       26 if ( defined $_[0][_VALUE_SR_IDX] ) {
347 10         19 my $promise_value_sr = $_[0][_VALUE_SR_IDX];
348 10 50       75 if ( my $value_sr = delete $_UNHANDLED_REJECTIONS{$promise_value_sr} ) {
349 0           warn "$_[0]: Unhandled rejection: $$value_sr";
350             }
351             }
352             }
353              
354             #----------------------------------------------------------------------
355              
356             # Future::AsyncAwait::Awaitable interface:
357              
358             # Future::AsyncAwait doesn’t retain a strong reference to its created
359             # promises, as a result of which we need to create a self-reference
360             # inside the promise. We’ll clear that self-reference once the promise
361             # is finished, which avoids memory leaks.
362             #
363             sub _immortalize {
364 8     8   19 my $method = $_[0];
365              
366 8         41 my $new = $_[1]->$method(@_[2 .. $#_]);
367              
368 8         61 $new->[_SELF_REF_IDX] = $new;
369             }
370              
371             sub AWAIT_NEW_DONE {
372 2   33 2 0 2695 _immortalize('resolve', (ref($_[0]) || $_[0]), $_[1]);
373             }
374              
375             sub AWAIT_NEW_FAIL {
376 2   33 2 0 5222 _immortalize('reject', (ref($_[0]) || $_[0]), $_[1]);
377             }
378              
379             sub AWAIT_CLONE {
380 4     4 0 4039 _immortalize('new', ref($_[0]), \&_noop);
381             }
382              
383             sub AWAIT_DONE {
384 4     4 0 552 my $copy = $_[1];
385              
386 4         20 $_[0]->_settle_now(bless \$copy, _RESOLUTION_CLASS);
387             }
388              
389             sub AWAIT_FAIL {
390 2     2 0 6 my $copy = $_[1];
391              
392 2         10 $_[0]->_settle_now(bless(\$copy, _REJECTION_CLASS), 1);
393             }
394              
395             sub AWAIT_IS_READY {
396 12     12 0 85 !UNIVERSAL::isa( $_[0]->[_VALUE_SR_IDX], _PENDING_CLASS );
397             }
398              
399 42     42   419 use constant AWAIT_IS_CANCELLED => 0;
  42         95  
  42         7535  
400              
401             sub AWAIT_GET {
402 12     12 0 42 delete $_UNHANDLED_REJECTIONS{$_[0]->[_VALUE_SR_IDX]};
403              
404 12 100       55 return ${ $_[0]->[_VALUE_SR_IDX] } if UNIVERSAL::isa( $_[0]->[_VALUE_SR_IDX], _RESOLUTION_CLASS );
  8         40  
405              
406 4         8 my $err = ${ $_[0]->[_VALUE_SR_IDX] };
  4         11  
407 4 50       17 die $err if substr($err, -1) eq "\n";
408 4         503 Carp::croak $err;
409             }
410              
411 42     42   365 use constant _noop => ();
  42         143  
  42         4678  
412              
413             sub AWAIT_ON_READY {
414 2     2 0 21 $_[0][_ON_READY_IMMEDIATE_IDX] = $_[1];
415             }
416              
417             *AWAIT_CHAIN_CANCEL = *_noop;
418             *AWAIT_ON_CANCEL = *_noop;
419              
420             1;