File: XS.pm

package info (click to toggle)
libpromise-xs-perl 0.20-1
  • links: PTS
  • area: main
  • in suites: forky, sid, trixie
  • size: 908 kB
  • sloc: perl: 1,097; ansic: 355; makefile: 3
file content (332 lines) | stat: -rw-r--r-- 9,462 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package Promise::XS;

use strict;
use warnings;

our $VERSION;

BEGIN {
    $VERSION = '0.20';
}

=encoding utf-8

=head1 NAME

Promise::XS - Fast promises in Perl

=head1 SYNOPSIS

    use Promise::XS ();

    my $deferred = Promise::XS::deferred();

    # Do one of these once you have the result of your operation:
    $deferred->resolve( 'foo', 'bar' );
    $deferred->reject( 'oh', 'no!' );

    # Give this to your caller:
    my $promise = $deferred->promise();

The following aggregator functions are exposed:

    # Resolves with a list of arrayrefs, one per promise.
    # Rejects with the results from the first rejected promise.
    # Non-promises will be passed through as resolve values.
    my $all_p = Promise::XS::all( $promise1, $promise2, 'abc' .. );

    # Resolves/rejects with the results from the first
    # resolved or rejected promise.
    my $race_p = Promise::XS::race( $promise3, $promise4, .. );

For compatibility with preexisting libraries, C<all()> may also be called
as C<collect()>.

The following also exist:

    my $pre_resolved_promise = Promise::XS::resolved('already', 'done');

    my $pre_rejected_promise = Promise::XS::rejected('it’s', 'bad');

All of C<Promise::XS>’s static functions may be exported at load time,
e.g., C<use Promise::XS qw(deferred)>.

=head1 DESCRIPTION

=begin html

<a href='https://coveralls.io/github/FGasper/p5-Promise-XS?branch=master'><img src='https://coveralls.io/repos/github/FGasper/p5-Promise-XS/badge.svg?branch=master' alt='Coverage Status' /></a>

=end html

This module exposes a Promise interface with its major parts
implemented in XS for speed. It is a fork and refactor of
L<AnyEvent::XSPromises>. That module’s interface, a “bare-bones”
subset of that from L<Promises>, is retained.

=head1 STATUS

This module is stable, well-tested, and suitable for production use.

=head1 DIFFERENCES FROM ECMASCRIPT PROMISES

This library is built for compatibility with pre-existing Perl promise
libraries. It thus exhibits some salient differences from how
ECMAScript promises work:

=over

=item * Neither the C<resolve()> method of deferred objects
nor the C<resolved()> convenience function define behavior when given
a promise object.

=item * The C<all()> and C<race()> functions accept a list of promises,
not a “scalar-array-thing” (ECMAScript “arrays” being what in Perl we
call “array references”). So whereas in ECMAScript you do:

    Promise.all( [ promise1, promise2 ] );

… in this library it’s:

    Promise::XS::all( $promise1, $promise2 );

=item * Promise resolutions and rejections may contain multiple values.
(But see L</AVOID MULTIPLES> below.)

=back

See L<Promise::ES6> for an interface that imitates ECMAScript promises
more closely.

=head1 AVOID MULTIPLES

For compatibility with preexisting Perl promise libraries, Promise::XS
allows a promise to resolve or reject with multiple values. This behavior,
while eminently “perlish”, allows for some weird cases where the relevant
standards don’t apply: for example, what happens if multiple promises are
returned from a promise callback? Or even just a single promise plus extra
returns?

Promise::XS tries to help you catch such cases by throwing a warning
if multiple return values from a callback contain a promise as the
first member. For best results, though—and consistency with promise
implementations outside Perl—resolve/reject all promises with I<single>
values.

=head1 DIFFERENCES FROM L<Promises> ET AL.

=head2 Empty or uninitialized rejection values

Perl helpfully warns (under the C<warnings> pragma, anyhow) when you
C<die(undef)> since an uninitialized value isn’t useful as an error report
and likely indicates a problem in the error-handling logic.

Promise rejections fulfill the same role in asynchronous code that
exceptions do in synchronous code. Thus, Promise::XS mimics Perl’s behavior:
if a rejection value list lacks a defined value, a warning is thrown. This
can happen if the value list is either empty or contains exclusively
uninitialized values.

=head2 C<finally()>

This module implements ECMAScript’s C<finally()> interface, which differs
from that in some other Perl promise implementations.

Given the following …

    my $new = $p->finally( $callback );

=over

=item * C<$callback> receives I<no> arguments.

=item * If C<$callback> returns anything but a single, rejected promise,
C<$new> has the same status as C<$p>.

=item * If C<$callback> throws, or if it returns a single, rejected promise,
C<$new> is rejected with the relevant value(s).

=back

=head1 ASYNC/AWAIT SUPPORT

This module is L<Promise::AsyncAwait>-compatible.
Once you load that module you can do nifty stuff like:

    use Promise::AsyncAwait;

    async sub do_stuff {
        return 1 + await fetch_number_p();
    }

    my $one_plus_number = await do_stuff();

… which roughly equates to:

    sub do_stuff {
        return fetch_number_p()->then( sub { 1 + $foo } );
    }

    do_stuff->then( sub {
        $one_plus_number = shift;
    } );

B<NOTE:> As of this writing, DEBUGGING-enabled perls trigger assertion
failures in L<Future::AsyncAwait> (which underlies L<Promise::AsyncAwait>).
If you’re not sure what that means, you probably don’t need to worry. :)

=head1 EVENT LOOPS

By default this library uses no event loop. This is a generally usable
configuration; however, it’ll be a bit different from how promises usually
work in evented contexts (e.g., JavaScript) because callbacks will execute
immediately rather than at the end of the event loop as the Promises/A+
specification requires. Following this pattern facilitates use of recursive
promises without exceeding call stack limits.

To achieve full Promises/A+ compliance it’s necessary to integrate with
an event loop interface. This library supports three such interfaces:

=over

=item * L<AnyEvent>:

    Promise::XS::use_event('AnyEvent');

=item * L<IO::Async> - note the need for an L<IO::Async::Loop> instance
as argument:

    Promise::XS::use_event('IO::Async', $loop_object);

=item * L<Mojo::IOLoop>:

    Promise::XS::use_event('Mojo::IOLoop');

=back

Note that all three of the above are event loop B<interfaces>. They
aren’t event loops themselves, but abstractions over various event loops.
See each one’s documentation for details about supported event loops.

=head1 MEMORY LEAK DETECTION

Any promise created while C<$Promise::XS::DETECT_MEMORY_LEAKS> is truthy
will throw a warning if it survives until global destruction.

=head1 SUBCLASSING

You can re-bless a L<Promise::XS::Promise> instance into a different class,
and C<then()>, C<catch()>, and C<finally()> will assign their newly-created
promise into that other class. (It follows that the other class must subclass
L<Promise::XS::Promise>.) This can be useful, e.g., for implementing
mid-flight controls like cancellation.

=head1 TODO

=over

=item * C<all()> and C<race()> should ideally be implemented in XS.

=back

=head1 KNOWN ISSUES

=over

=item * Interpreter-based threads may or may not work.

=item * This module interacts badly with Perl’s fork() implementation on
Windows. There may be a workaround possible, but none is implemented for now.

=back

=cut

use Exporter 'import';
our @EXPORT_OK= qw/all collect deferred resolved rejected/;

use Promise::XS::Deferred ();
use Promise::XS::Promise ();

our $DETECT_MEMORY_LEAKS;

use constant DEFERRAL_CR => {
    AnyEvent => \&Promise::XS::Deferred::set_deferral_AnyEvent,
    'IO::Async' => \&Promise::XS::Deferred::set_deferral_IOAsync,
    'Mojo::IOLoop' => \&Promise::XS::Deferred::set_deferral_Mojo,
};

# convenience
*deferred = *Promise::XS::Deferred::create;

require XSLoader;
XSLoader::load('Promise::XS', $VERSION);

sub use_event {
    my ($name, @args) = @_;

    if (my $cr = DEFERRAL_CR()->{$name}) {
        $cr->(@args);
    }
    else {
        my @known = sort keys %{ DEFERRAL_CR() };
        die( __PACKAGE__ . ": unknown event engine: $name (must be one of: @known)" );
    }
}

# called from XS
sub _convert_to_our_promise {
    my $thenable = shift;
    my $deferred= Promise::XS::Deferred::create();
    my $called;

    local $@;
    eval {
        $thenable->then(sub {
            return if $called++;
            $deferred->resolve(@_);
        }, sub {
            return if $called++;
            $deferred->reject(@_);
        });
        1;
    } or do {
        my $error= $@;
        if (!$called++) {
            $deferred->reject($error);
        }
    };

    # This promise is purely internal, so let’s not warn
    # when its rejection is unhandled.
    $deferred->clear_unhandled_rejection();

    return $deferred->promise;
}

#----------------------------------------------------------------------
# Aggregator functions
sub all {
    return Promise::XS::Promise->all(@_);
}

sub race {
    return Promise::XS::Promise->race(@_);
}

# Compatibility with other promise interfaces.
*collect = *all;

#----------------------------------------------------------------------

=head1 SEE ALSO

Besides L<AnyEvent::XSPromises> and L<Promises>, you may like L<Promise::ES6>,
which mimics L<ECMAScript’s “Promise” class|https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise> as much as possible.
It can even
(experimentally) use this module as a backend, which helps but is still
significantly slower than using this module directly.

=cut

1;