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   731 use Evo::Base -base;
  29         37  
  29         165  
3 29     29   4383 use Promises6::Util ':all';
  29         40  
  29         4046  
4 29     29   142 use Scalar::Util;
  29         39  
  29         1177  
5 29     29   111923 use Hash::Util::FieldHash 'fieldhash';
  29         23498  
  29         16988  
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 809 sub thenables { $THENABLES{$_[1]} //= {}; }
32              
33 231 50   231 1 4813 sub resolve($self, $deferred, $val) {
  231 50       432  
  231         242  
  231         195  
  231         212  
  231         203  
34              
35             # not a promise, but deferred protect from mem leaks
36 231 100 100     1651 return $deferred->reject("circular reference detected $val")
      100        
37             if $val && Scalar::Util::blessed $val && $val eq $deferred;
38              
39 229 100       564 return $self->resolve_promise($deferred, $val) if is_promise($val);
40 207 100       916 return $self->resolve_thenable($deferred, $val) if is_thenable($val);
41 169         614 $deferred->change_state(FULFILLED, $val);
42             }
43              
44 22 50   22 1 321 sub resolve_promise($self, $deferred, $promise) {
  22 50       66  
  22         35  
  22         26  
  22         59  
  22         25  
45 22         400 my $xd = $promise->deferred;
46              
47 22 100       189 return $deferred->reject("circular reference detected $promise")
48             if $xd == $deferred;
49 19 100       56 return $deferred->change_state($xd->result->@*) if $xd->state != PENDING;
50              
51 6         138 $xd->subscribe($deferred->builder->listener(deferred => $deferred));
52 6         133 $deferred->is_adaptor(1);
53             }
54              
55 38 50   38 1 98 sub resolve_thenable($self, $deferred, $thenable) {
  38 50       86  
  38         47  
  38         41  
  38         40  
  38         36  
56              
57 38         42 my $was_called = 0;
58 38         100 my $registered = $self->thenables($deferred);
59 38         107 $registered->{$thenable}++;
60              
61             # resolved cb, if called exceptions will be skipped after that
62 30 50   30   2293 my $resolve = sub($v) {
  30 50       74  
  30         44  
  30         26  
63 30 100       72 return if $was_called++;
64 28 100       134 return $deferred->reject("Circular thenable $v") if $registered->{$v}++;
65 23         68 $deferred->resolve($v);
66 38         140 };
67              
68             # reject immidiately changes a state
69 11 50   11   440 my $reject = sub($r) {
  11 50       24  
  11         13  
  11         12  
70 11 100       29 return if $was_called++;
71 9         32 $deferred->reject($r);
72 38         103 };
73              
74             # resolution 3.III.d
75 38         41 my $err;
76 38         37 CATCH: {
77 38         38 local $@;
78 38         48 eval { $thenable->then($resolve, $reject) };
  38         111  
79 38         178 $err = $@;
80             }
81 38 100 100     327 $deferred->reject($err) if $err && !$was_called;
82             }
83              
84              
85             1;
86              
87             __END__