File Coverage

blib/lib/Promise/ES6.pm
Criterion Covered Total %
statement 98 100 98.0
branch 20 24 83.3
condition n/a
subroutine 19 20 95.0
pod 1 8 12.5
total 138 152 90.7


line stmt bran cond sub pod time code
1             package Promise::ES6;
2              
3 16     16   1382532 use strict;
  16         135  
  16         471  
4 16     16   83 use warnings;
  16         39  
  16         18597  
5              
6             our $VERSION = '0.01-TRIAL2';
7              
8             =encoding utf-8
9              
10             =head1 NAME
11              
12             Promise::ES6 - ES6-style promises in Perl
13              
14             =head1 SYNOPSIS
15              
16             my $promise = Promise::ES6->new( sub {
17             my ($resolve_cr, $reject_cr) = @_;
18              
19             # ..
20             } );
21              
22             my $promise2 = $promise->then( sub { .. }, sub { .. } );
23              
24             my $promise3 = $promise->catch( sub { .. } );
25              
26             my $promise4 = $promise->finally( sub { .. } );
27              
28             my $resolved = Promise::ES6->resolve(5);
29             my $rejected = Promise::ES6->reject('nono');
30              
31             my $all_promise = Promise::ES6->all( \@promises );
32              
33             my $race_promise = Promise::ES6->race( \@promises );
34              
35             =head1 DESCRIPTION
36              
37             This is a rewrite of L that implements fixes for
38             certain bugs that proved hard to fix in the original code. This module
39             also removes superfluous dependencies on L and L.
40              
41             The interface is the same, except:
42              
43             =over
44              
45             =item * Promise resolutions and rejections take exactly one argument,
46             not a list. (This accords with the standard.)
47              
48             =item * A C method is defined.
49              
50             =back
51              
52             =head1 COMPATIBILITY
53              
54             Right now this doesn’t try for interoperability with other promise
55             classes. If that’s something you want, make a feature request.
56              
57             =head1 SEE ALSO
58              
59             If you’re not sure of what promises are, there are several good
60             introductions to the topic. You might start with
61             L.
62              
63             =cut
64              
65             sub new {
66 73     73 0 86632 my ($class, $cr) = @_;
67              
68 73         236 my $self = bless {}, $class;
69              
70 73     44   266 my $resolver = sub { $self->_finish( resolve => $_[0]) };
  44         1506235  
71 73     11   342 my $rejecter = sub { $self->_finish( reject => $_[0]) };
  11         101275  
72              
73 73         137 local $@;
74 73 100       133 if ( !eval { $cr->( $resolver, $rejecter ); 1 } ) {
  73         197  
  64         2966  
75 7         162 $self->_finish( reject => $@ );
76             }
77              
78 71         899 return $self;
79             }
80              
81             sub then {
82 85     85 0 13901 my ($self, $on_resolve, $on_reject) = @_;
83              
84 85         378 my $new = {
85             _on_resolve => $on_resolve,
86             _on_reject => $on_reject,
87             };
88              
89 85         259 bless $new, (ref $self);
90              
91 85 100       282 if ($self->{'_finished_how'}) {
92 59         147 $new->_finish( $self->{'_finished_how'} => $self->{'_value'} );
93             }
94             else {
95 26         87 push @{ $self->{'_dependents'} }, $new;
  26         174  
96             }
97              
98 85         255 return $new;
99             }
100              
101 5     5 0 30 sub catch { return $_[0]->then( undef, $_[1] ) }
102              
103             sub finally {
104 0     0 1 0 my ($self, $todo_cr) = @_;
105              
106 0         0 return $self->then( $todo_cr, $todo_cr );
107             }
108              
109             sub _finish {
110 141     141   487 my ($self, $how, $value) = @_;
111              
112 141 50       518 die "$self already finished!" if $self->{'_finished_how'};
113              
114 141         223 local $@;
115              
116 141 100       611 if ($self->{"_on_$how"}) {
117 78 100       139 if ( eval { $value = $self->{"_on_$how"}->($value); 1 } ) {
  78         275  
  77         4683  
118 77         137 $how = 'resolve';
119             }
120             else {
121 1         11 $how = 'reject';
122 1         2 $value = $@;
123             }
124             }
125              
126 141         215 my $repromise_if_needed;
127             $repromise_if_needed = sub {
128 145     145   351 my ($repromise_how, $repromise_value) = @_;
129              
130 145 100       284 if (eval { $repromise_value->isa(__PACKAGE__) }) {
  145         1149  
131 4         40 $self->{'_unresolved_value'} = $repromise_value;
132              
133             $repromise_value->then(
134 3         46 sub { $repromise_if_needed->( resolve => $_[0]) },
135             sub {
136 1         5 $repromise_if_needed->( reject => $_[0]);
137             },
138 4         133 );
139             }
140             else {
141 141         426 $self->{'_value'} = $repromise_value;
142 141         390 $self->{'_finished_how'} = $repromise_how;
143              
144 141         218 $_->_finish($repromise_how, $repromise_value) for @{ $self->{'_dependents'} };
  141         585  
145             }
146 141         894 };
147              
148 141         531 $repromise_if_needed->($how, $value);
149              
150 141         722 return;
151             }
152              
153             #----------------------------------------------------------------------
154              
155             sub resolve {
156 6     6 0 1827 my ($class, $value) = @_;
157              
158             return $class->new(sub {
159 6     6   14 my ($resolve, undef) = @_;
160 6         14 $resolve->($value);
161 6         65 });
162             }
163              
164             sub reject {
165 1     1 0 1645 my ($class, $reason) = @_;
166              
167             return $class->new(sub {
168 1     1   12 my (undef, $reject) = @_;
169 1         4 $reject->($reason);
170 1         8 });
171             }
172              
173             sub all {
174 6     6 0 2374 my ($class, $iterable) = @_;
175 6 100       66 my @promises = map { _is_promise($_) ? $_ : $class->resolve($_) } @$iterable;
  15         47  
176              
177             return $class->new(sub {
178 6     6   15 my ($resolve, $reject) = @_;
179 6         11 my $unresolved_size = scalar(@promises);
180 6         16 for my $promise (@promises) {
181             $promise->then(sub {
182 13         27 my ($value) = @_;
183 13         20 $unresolved_size--;
184 13 100       40 if ($unresolved_size <= 0) {
185 4         11 $resolve->([ map { $_->{_value} } @promises ]);
  11         32  
186             }
187             }, sub {
188 2         6 my ($reason) = @_;
189 2         22 $reject->($reason);
190 15         101 });
191             }
192 6         41 });
193             }
194              
195             sub race {
196 4     4 0 1737 my ($class, $iterable) = @_;
197 4 50       74 my @promises = map { _is_promise($_) ? $_ : $class->resolve($_) } @$iterable;
  8         78  
198              
199             return Promise::ES6::Race->new(sub {
200 4     4   7 my ($resolve, $reject) = @_;
201              
202 4         14 my $is_done;
203              
204 4         10 for my $promise (@promises) {
205 8 100       16 last if $is_done;
206              
207             $promise->then(sub {
208 3         8 my ($value) = @_;
209              
210 3 50       7 return if $is_done;
211 3         3 $is_done = 1;
212              
213 3         7 $resolve->($value);
214             }, sub {
215 1         11 my ($reason) = @_;
216              
217 1 50       23 return if $is_done;
218 1         12 $is_done = 1;
219              
220 1         22 $reject->($reason);
221 7         124 });
222             }
223 4         175 });
224             }
225              
226             sub _is_promise {
227 23     23   55 local $@;
228 23         64 return eval { $_[0]->isa(__PACKAGE__) };
  23         240  
229             }
230              
231             package Promise::ES6::Race;
232              
233 16     16   606 use parent -norequire => 'Promise::ES6';
  16         335  
  16         124  
234              
235             1;