# 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. # STATUS This module is in use in production and, backed by a pretty extensive set of regression tests, may be considered stable. # 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](https://github.com/promises-aplus/promises-tests) 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 catch 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. If you need this functionality, then, 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: - 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. You should normally enable this flag in a development environment. - 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 (i.e., anything pre-5.16) you can imitate it thus: use constant _has_current_sub => eval "use feature 'current_sub'"; 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.