File Coverage

blib/lib/Promise/XS.pm
Criterion Covered Total %
statement 30 43 69.7
branch 2 10 20.0
condition n/a
subroutine 9 13 69.2
pod 2 3 66.6
total 43 69 62.3


line stmt bran cond sub pod time code
1             package Promise::XS;
2              
3 26     26   1866366 use strict;
  26         315  
  26         747  
4 26     26   134 use warnings;
  26         46  
  26         1089  
5              
6             our $VERSION;
7              
8             BEGIN {
9 26     26   1267 $VERSION = '0.20';
10             }
11              
12             =encoding utf-8
13              
14             =head1 NAME
15              
16             Promise::XS - Fast promises in Perl
17              
18             =head1 SYNOPSIS
19              
20             use Promise::XS ();
21              
22             my $deferred = Promise::XS::deferred();
23              
24             # Do one of these once you have the result of your operation:
25             $deferred->resolve( 'foo', 'bar' );
26             $deferred->reject( 'oh', 'no!' );
27              
28             # Give this to your caller:
29             my $promise = $deferred->promise();
30              
31             The following aggregator functions are exposed:
32              
33             # Resolves with a list of arrayrefs, one per promise.
34             # Rejects with the results from the first rejected promise.
35             # Non-promises will be passed through as resolve values.
36             my $all_p = Promise::XS::all( $promise1, $promise2, 'abc' .. );
37              
38             # Resolves/rejects with the results from the first
39             # resolved or rejected promise.
40             my $race_p = Promise::XS::race( $promise3, $promise4, .. );
41              
42             For compatibility with preexisting libraries, C may also be called
43             as C.
44              
45             The following also exist:
46              
47             my $pre_resolved_promise = Promise::XS::resolved('already', 'done');
48              
49             my $pre_rejected_promise = Promise::XS::rejected('it’s', 'bad');
50              
51             All of C’s static functions may be exported at load time,
52             e.g., C.
53              
54             =head1 DESCRIPTION
55              
56             =begin html
57              
58             Coverage Status
59              
60             =end html
61              
62             This module exposes a Promise interface with its major parts
63             implemented in XS for speed. It is a fork and refactor of
64             L. That module’s interface, a “bare-bones”
65             subset of that from L, is retained.
66              
67             =head1 STATUS
68              
69             This module is stable, well-tested, and suitable for production use.
70              
71             =head1 DIFFERENCES FROM ECMASCRIPT PROMISES
72              
73             This library is built for compatibility with pre-existing Perl promise
74             libraries. It thus exhibits some salient differences from how
75             ECMAScript promises work:
76              
77             =over
78              
79             =item * Neither the C method of deferred objects
80             nor the C convenience function define behavior when given
81             a promise object.
82              
83             =item * The C and C functions accept a list of promises,
84             not a “scalar-array-thing” (ECMAScript “arrays” being what in Perl we
85             call “array references”). So whereas in ECMAScript you do:
86              
87             Promise.all( [ promise1, promise2 ] );
88              
89             … in this library it’s:
90              
91             Promise::XS::all( $promise1, $promise2 );
92              
93             =item * Promise resolutions and rejections may contain multiple values.
94             (But see L below.)
95              
96             =back
97              
98             See L for an interface that imitates ECMAScript promises
99             more closely.
100              
101             =head1 AVOID MULTIPLES
102              
103             For compatibility with preexisting Perl promise libraries, Promise::XS
104             allows a promise to resolve or reject with multiple values. This behavior,
105             while eminently “perlish”, allows for some weird cases where the relevant
106             standards don’t apply: for example, what happens if multiple promises are
107             returned from a promise callback? Or even just a single promise plus extra
108             returns?
109              
110             Promise::XS tries to help you catch such cases by throwing a warning
111             if multiple return values from a callback contain a promise as the
112             first member. For best results, though—and consistency with promise
113             implementations outside Perl—resolve/reject all promises with I
114             values.
115              
116             =head1 DIFFERENCES FROM L ET AL.
117              
118             =head2 Empty or uninitialized rejection values
119              
120             Perl helpfully warns (under the C pragma, anyhow) when you
121             C since an uninitialized value isn’t useful as an error report
122             and likely indicates a problem in the error-handling logic.
123              
124             Promise rejections fulfill the same role in asynchronous code that
125             exceptions do in synchronous code. Thus, Promise::XS mimics Perl’s behavior:
126             if a rejection value list lacks a defined value, a warning is thrown. This
127             can happen if the value list is either empty or contains exclusively
128             uninitialized values.
129              
130             =head2 C
131              
132             This module implements ECMAScript’s C interface, which differs
133             from that in some other Perl promise implementations.
134              
135             Given the following …
136              
137             my $new = $p->finally( $callback );
138              
139             =over
140              
141             =item * C<$callback> receives I arguments.
142              
143             =item * If C<$callback> returns anything but a single, rejected promise,
144             C<$new> has the same status as C<$p>.
145              
146             =item * If C<$callback> throws, or if it returns a single, rejected promise,
147             C<$new> is rejected with the relevant value(s).
148              
149             =back
150              
151             =head1 ASYNC/AWAIT SUPPORT
152              
153             This module is L-compatible.
154             Once you load that module you can do nifty stuff like:
155              
156             use Promise::AsyncAwait;
157              
158             async sub do_stuff {
159             return 1 + await fetch_number_p();
160             }
161              
162             my $one_plus_number = await do_stuff();
163              
164             … which roughly equates to:
165              
166             sub do_stuff {
167             return fetch_number_p()->then( sub { 1 + $foo } );
168             }
169              
170             do_stuff->then( sub {
171             $one_plus_number = shift;
172             } );
173              
174             B As of this writing, DEBUGGING-enabled perls trigger assertion
175             failures in L (which underlies L).
176             If you’re not sure what that means, you probably don’t need to worry. :)
177              
178             =head1 EVENT LOOPS
179              
180             By default this library uses no event loop. This is a generally usable
181             configuration; however, it’ll be a bit different from how promises usually
182             work in evented contexts (e.g., JavaScript) because callbacks will execute
183             immediately rather than at the end of the event loop as the Promises/A+
184             specification requires. Following this pattern facilitates use of recursive
185             promises without exceeding call stack limits.
186              
187             To achieve full Promises/A+ compliance it’s necessary to integrate with
188             an event loop interface. This library supports three such interfaces:
189              
190             =over
191              
192             =item * L:
193              
194             Promise::XS::use_event('AnyEvent');
195              
196             =item * L - note the need for an L instance
197             as argument:
198              
199             Promise::XS::use_event('IO::Async', $loop_object);
200              
201             =item * L:
202              
203             Promise::XS::use_event('Mojo::IOLoop');
204              
205             =back
206              
207             Note that all three of the above are event loop B. They
208             aren’t event loops themselves, but abstractions over various event loops.
209             See each one’s documentation for details about supported event loops.
210              
211             =head1 MEMORY LEAK DETECTION
212              
213             Any promise created while C<$Promise::XS::DETECT_MEMORY_LEAKS> is truthy
214             will throw a warning if it survives until global destruction.
215              
216             =head1 SUBCLASSING
217              
218             You can re-bless a L instance into a different class,
219             and C, C, and C will assign their newly-created
220             promise into that other class. (It follows that the other class must subclass
221             L.) This can be useful, e.g., for implementing
222             mid-flight controls like cancellation.
223              
224             =head1 TODO
225              
226             =over
227              
228             =item * C and C should ideally be implemented in XS.
229              
230             =back
231              
232             =head1 KNOWN ISSUES
233              
234             =over
235              
236             =item * Interpreter-based threads may or may not work.
237              
238             =item * This module interacts badly with Perl’s fork() implementation on
239             Windows. There may be a workaround possible, but none is implemented for now.
240              
241             =back
242              
243             =cut
244              
245 26     26   170 use Exporter 'import';
  26         47  
  26         1486  
246             our @EXPORT_OK= qw/all collect deferred resolved rejected/;
247              
248 26     26   10661 use Promise::XS::Deferred ();
  26         73  
  26         501  
249 26     26   10363 use Promise::XS::Promise ();
  26         64  
  26         1399  
250              
251             our $DETECT_MEMORY_LEAKS;
252              
253 26         12213 use constant DEFERRAL_CR => {
254             AnyEvent => \&Promise::XS::Deferred::set_deferral_AnyEvent,
255             'IO::Async' => \&Promise::XS::Deferred::set_deferral_IOAsync,
256             'Mojo::IOLoop' => \&Promise::XS::Deferred::set_deferral_Mojo,
257 26     26   170 };
  26         52  
258              
259             # convenience
260             *deferred = *Promise::XS::Deferred::create;
261              
262             require XSLoader;
263             XSLoader::load('Promise::XS', $VERSION);
264              
265             sub use_event {
266 0     0 0 0 my ($name, @args) = @_;
267              
268 0 0       0 if (my $cr = DEFERRAL_CR()->{$name}) {
269 0         0 $cr->(@args);
270             }
271             else {
272 0         0 my @known = sort keys %{ DEFERRAL_CR() };
  0         0  
273 0         0 die( __PACKAGE__ . ": unknown event engine: $name (must be one of: @known)" );
274             }
275             }
276              
277             # called from XS
278             sub _convert_to_our_promise {
279 1     1   3344 my $thenable = shift;
280 1         4 my $deferred= Promise::XS::Deferred::create();
281 1         2 my $called;
282              
283 1         3 local $@;
284             eval {
285             $thenable->then(sub {
286 0 0   0   0 return if $called++;
287 0         0 $deferred->resolve(@_);
288             }, sub {
289 1 50   1   9 return if $called++;
290 1         7 $deferred->reject(@_);
291 1         9 });
292 1         8 1;
293 1 50       2 } or do {
294 0         0 my $error= $@;
295 0 0       0 if (!$called++) {
296 0         0 $deferred->reject($error);
297             }
298             };
299              
300             # This promise is purely internal, so let’s not warn
301             # when its rejection is unhandled.
302 1         4 $deferred->clear_unhandled_rejection();
303              
304 1         17 return $deferred->promise;
305             }
306              
307             #----------------------------------------------------------------------
308             # Aggregator functions
309             sub all {
310 0     0 1   return Promise::XS::Promise->all(@_);
311             }
312              
313             sub race {
314 0     0 1   return Promise::XS::Promise->race(@_);
315             }
316              
317             # Compatibility with other promise interfaces.
318             *collect = *all;
319              
320             #----------------------------------------------------------------------
321              
322             =head1 SEE ALSO
323              
324             Besides L and L, you may like L,
325             which mimics L as much as possible.
326             It can even
327             (experimentally) use this module as a backend, which helps but is still
328             significantly slower than using this module directly.
329              
330             =cut
331              
332             1;