# NAME
Promise::ES6 - ES6-style promises in Perl
# SYNOPSIS
my $promise = Promise::ES6->new( sub {
my ($resolve_cr, $reject_cr) = @_;
# ..
} );
my $promise2 = $promise->then( sub { .. }, sub { .. } );
my $promise3 = $promise->catch( sub { .. } );
my $promise4 = $promise->finally( sub { .. } );
my $resolved = Promise::ES6->resolve(5);
my $rejected = Promise::ES6->reject('nono');
my $all_promise = Promise::ES6->all( \@promises );
my $race_promise = Promise::ES6->race( \@promises );
# DESCRIPTION
This module provides a Perl implementation of [promises](
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises), a useful pattern
for coordinating asynchronous tasks.
Unlike most other promise implementations on CPAN, this module
mimics ECMAScript 6’s [Promise](
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
interface. As the SYNOPSIS above shows, you can thus use patterns from
JavaScript in Perl with only minimal changes needed to accommodate language
syntax.
This is a rewrite of an earlier module, [Promise::Tiny](
https://metacpan.org/pod/Promise::Tiny). It fixes several
bugs and superfluous dependencies in the original.
# INTERFACE NOTES
- Promise resolutions and rejections accept exactly one argument,
not a list.
- Unhandled rejections are reported via `warn()`. (See below
for details.)
- The Promises/A+ test suite avoids testing the case where an “executor”
function’s resolve callback itself receives another promise, e.g.:
my $p = Promise::ES6->new( sub ($res) {
$res->( Promise::ES6->resolve(123) );
} );
What will $p’s resolution value be? 123, or the promise that wraps it?
This module favors conformity with the ES6 standard, which
[indicates intent](
https://www.ecma-international.org/ecma-262/6.0/#sec-promise-executor) that $p’s resolution value be 123.
# COMPATIBILITY
This module considers any object that has a `then()` method to be a promise.
Note that, in the case of [Future](
https://metacpan.org/pod/Future), this will yield a “false-positive”, as
Future is not compatible with promises.
(See [Promise::ES6::Future](
https://metacpan.org/pod/Promise::ES6::Future) for more tools to interact with [Future](
https://metacpan.org/pod/Future).)
# UNHANDLED REJECTIONS
This module’s handling of unhandled rejections has changed over time.
The current behavior is: if any rejected promise is DESTROYed without first
having received a failure callback, a warning is thrown.
# SYNCHRONOUS OPERATION
In JavaScript, the following …
Promise.resolve().then( () => console.log(1) );
console.log(2);
… will log `2` then `1` because JavaScript’s `then()` defers execution
of its callbacks until the end of the current iteration through JavaScript’s
event loop.
Perl, of course, has no built-in event loop. This module’s `then()` method,
thus, when called on a promise that is already
“settled” (i.e., not pending), will run the appropriate callback
_immediately_. That means that this:
Promise::ES6->resolve(0)->then( sub { print 1 } );
print 2;
… will print `12` instead of `21`.
This is an intentional divergence from
[the Promises/A+ specification](
https://promisesaplus.com/#point-34).
A key advantage of this design is that Promise::ES6 instances can abstract
over whether a given function works synchronously or asynchronously.
If you want a Promises/A+-compliant implementation, look at
[Promise::ES6::IOAsync](
https://metacpan.org/pod/Promise::ES6::IOAsync), [Promise::ES6::Mojo](
https://metacpan.org/pod/Promise::ES6::Mojo), or
[Promise::ES6::AnyEvent](
https://metacpan.org/pod/Promise::ES6::AnyEvent) in this distribution. CPAN provides other
alternatives.
# CANCELLATION
Promises have never provided a standardized solution for cancellation—i.e.,
aborting an in-process operation. So, if you need this functionality, you’ll
have to implement it yourself. Two ways of doing this are:
- Subclass Promise::ES6 and provide cancellation logic in that
subclass. See [DNS::Unbound::AsyncQuery](
https://metacpan.org/pod/DNS::Unbound::AsyncQuery)’s implementation for an
example of this.
- Implement the cancellation on a request object that your
“promise-creator” also consumes. This is probably the more straightforward
approach but requires that there
be some object or ID besides the promise that uniquely identifies the action
to be canceled. See [Net::Curl::Promiser](
https://metacpan.org/pod/Net::Curl::Promiser) for an example of this approach.
You’ll need to decide if it makes more sense for your application to leave
a canceled query in the “pending” state or to “settle” (i.e., resolve or
reject) it. All things being equal, I feel the first approach is the most
intuitive.
# MEMORY LEAKS
It’s easy to create inadvertent memory leaks using promises in Perl.
Here are a few “pointers” (heh) to bear in mind:
- As of version 0.07, any Promise::ES6 instances that are created while
`$Promise::ES6::DETECT_MEMORY_LEAKS` is set to a truthy value are
“leak-detect-enabled”, which means that if they survive until their original
process’s global destruction, a warning is triggered.
- If your application needs recursive promises (e.g., to poll
iteratively for completion of a task), the `current_sub` feature (i.e.,
`__SUB__`) may help you avoid memory leaks. In Perl versions that don’t
support this feature you can imitate it thus:
use constant _has_current_sub => $^V ge v5.16.0;
use if _has_current_sub(), feature => 'current_sub';
my $cb;
$cb = sub {
my $current_sub = do {
no strict 'subs';
_has_current_sub() ? __SUB__ : eval '$cb';
};
}
Of course, it’s better if you can avoid doing that. :)
- Garbage collection before Perl 5.18 seems to have been buggy.
If you work with such versions and end up chasing leaks,
try manually deleting as many references/closures as possible. See
`t/race_success.t` for a notated example.
You may also (counterintuitively, IMO) find that this:
my ($resolve, $reject);
my $promise = Promise::ES6->new( sub { ($resolve, $reject) = @_ } );
# … etc.
… works better than:
my $promise = Promise::ES6->new( sub {
my ($resolve, $reject) = @_;
# … etc.
} );
# SEE ALSO
If you’re not sure of what promises are, there are several good
introductions to the topic. You might start with
[this one](
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises).
[Promise::XS](
https://metacpan.org/pod/Promise::XS) is a lot like this library but implemented mostly in XS for
speed. It derives from [AnyEvent::XSPromises](
https://metacpan.org/pod/AnyEvent::XSPromises).
[Promises](
https://metacpan.org/pod/Promises) is another pure-Perl Promise implementation.
[Future](
https://metacpan.org/pod/Future) fills a role similar to that of promises.
CPAN contains a number of other modules that implement promises. I think
mine are the nicest :), but YMMV. Enjoy!
# LICENSE & COPYRIGHT
Copyright 2019-2020 Gasper Software Consulting.
This library is licensed under the same terms as Perl itself.