File Coverage

blib/lib/Promises6/Resolver.pm
Criterion Covered Total %
statement 65 65 100.0
branch 28 38 73.6
condition 11 11 100.0
subroutine 10 10 100.0
pod 4 4 100.0
total 118 128 92.1


line stmt bran cond sub pod time code
1             package Promises6::Resolver;
2 29     29   690 use Evo -class, -modern;
  29         29  
  29         131  
3 29     29   5626 use Promises6::Util ':all';
  29         45  
  29         2562  
4 29     29   124 use Scalar::Util;
  29         34  
  29         980  
5 29     29   14136 use Hash::Util::FieldHash 'fieldhash';
  29         19360  
  29         13236  
6              
7             # that's the harder part of specification, because some paragraphs are not so obvious
8             # even JS Q doesn't implement it in the right way. But we'll take a try
9             #
10             # SpecA+ .. a thenable that participates in a circular thenable chain..
11             # we do reject in cb because if somehow the same thenable won't call
12             # resolve cb, than there are no infinite. Also race can call the same
13             # thenable twice.
14              
15             # if then will call resolve or reject with a value and then die,
16             # our deferred will remain unchanged, because will be fulfilled with a value
17             # but we need to track a case when then calls $resolved and die, and send_msg
18             # is subclassed with next_tick. because we use the same deferred, wich
19             # remains PENDING.
20             # So we're going to use a counter and ignore an error if $resolve was called
21             #
22             # Also we have cover a case when thenable calls our callback twice, first time
23             # with thenable or other promise, and second with a plane value. We should
24             # ignore the second call. So we track the fact that callback was called on both
25             # onResolve and onReject. see then/thenable_call_cbs_several_times.t
26              
27              
28             fieldhash my %THENABLES;
29             has 'builder';
30              
31 39   100 39 1 259 sub thenables { $THENABLES{$_[1]} //= {}; }
32              
33 231 50   231 1 1066 sub resolve($self, $deferred, $val) {
  231 50       321  
  231         174  
  231         172  
  231         181  
  231         196  
34              
35             # not a promise, but deferred protect from mem leaks
36 231 100 100     1233 return $deferred->reject("circular reference detected $val")
      100        
37             if $val && Scalar::Util::blessed $val && $val eq $deferred;
38              
39 229 100       428 return $self->resolve_promise($deferred, $val) if is_promise($val);
40 207 100       731 return $self->resolve_thenable($deferred, $val) if is_thenable($val);
41 169         431 $deferred->change_state(FULFILLED, $val);
42             }
43              
44 22 50   22 1 195 sub resolve_promise($self, $deferred, $promise) {
  22 50       37  
  22         22  
  22         26  
  22         19  
  22         16  
45 22         44 my $xd = $promise->deferred;
46              
47 22 100       121 return $deferred->reject("circular reference detected $promise")
48             if $xd == $deferred;
49 19 100       40 return $deferred->change_state($xd->result->@*) if $xd->state != PENDING;
50              
51 6         18 $xd->subscribe($deferred->builder->listener(deferred => $deferred));
52 6         16 $deferred->is_adaptor(1);
53             }
54              
55 38 50   38 1 61 sub resolve_thenable($self, $deferred, $thenable) {
  38 50       61  
  38         34  
  38         27  
  38         33  
  38         29  
56              
57 38         31 my $was_called = 0;
58 38         56 my $registered = $self->thenables($deferred);
59 38         76 $registered->{$thenable}++;
60              
61             # resolved cb, if called exceptions will be skipped after that
62 30 50   30   1963 my $resolve = sub($v) {
  30 50       55  
  30         32  
  30         29  
63 30 100       61 return if $was_called++;
64 28 100       102 return $deferred->reject("Circular thenable $v") if $registered->{$v}++;
65 23         63 $deferred->resolve($v);
66 38         117 };
67              
68             # reject immidiately changes a state
69 11 50   11   345 my $reject = sub($r) {
  11 50       18  
  11         13  
  11         9  
70 11 100       23 return if $was_called++;
71 9         27 $deferred->reject($r);
72 38         76 };
73              
74             # resolution 3.III.d
75 38         25 my $err;
76 38         33 CATCH: {
77 38         30 local $@;
78 38         44 eval { $thenable->then($resolve, $reject) };
  38         94  
79 38         131 $err = $@;
80             }
81 38 100 100     246 $deferred->reject($err) if $err && !$was_called;
82             }
83              
84              
85             1;
86              
87             __END__