# NAME Promise::ES6 - ES6-style promises in Perl # SYNOPSIS use Promise::ES6; # OPTIONAL. And see below for other options. Promise::ES6::use_event('IO::Async', $loop); 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 ); my $allsettled_promise = Promise::ES6->allSettled( \@promises ); # DESCRIPTION <div> <a href='https://coveralls.io/github/FGasper/p5-Promise-ES6?branch=master'><img src='https://coveralls.io/repos/github/FGasper/p5-Promise-ES6/badge.svg?branch=master' alt='Coverage Status' /></a> </div> 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�䏭 [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.) - Undefined or empty rejection values trigger a warning. This provides the same value as Perl�䏭 own warning on `die(undef)`. - The [Promises/A+ test suite](https://github.com/promises-aplus/promises-tests) avoids testing the case where an �𩃀xecutor�� function�䏭 resolve callback itself receives another promise, e.g.: my $p = Promise::ES6->new( sub ($res) { $res->( Promise::ES6->resolve(123) ); } ); What will $p�䏭 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�䏭 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 �𠨑alse-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).) # **EXPERIMENTAL:** ASYNC/AWAIT SUPPORT This module implements [Future::AsyncAwait::Awaitable](https://metacpan.org/pod/Future::AsyncAwait::Awaitable). This lets you do nifty stuff like: use Future::AsyncAwait; async sub do_stuff { my $foo = await fetch_number_p(); # NB: The real return is a promise that provides this value: return 1 + $foo; } 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; } ); # UNHANDLED REJECTIONS This module�䏭 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 VS. ASYNCHRONOUS OPERATION In JavaScript, the following �� Promise.resolve().then( () => console.log(1) ); console.log(2); �� will log `2` then `1` because JavaScript�䏭 `then()` defers execution of its callbacks until between iterations through JavaScript�䏭 event loop. Perl, of course, has no built-in event loop. This module accommodates that by implementing **synchronous** promises by default rather than asynchronous ones. This means that all promise callbacks run _immediately_ rather than between iterations of an event loop. As a result, this: Promise::ES6->resolve(0)->then( sub { print 1 } ); print 2; �� will print `12` instead of `21`. One effect of this is that Promise::ES6, in its default configuration, is agnostic regarding event loop interfaces: no special configuration is needed for any specific event loop. In fact, you don�脌 even _need_ an event loop at all, which might be useful for abstracting over whether a given function works synchronously or asynchronously. The disadvantage of synchronous promises�𥿡esides not being _quite_ the same promises that we expect from JS�㻳s that recursive promises can exceed call stack limits. For example, the following (admittedly contrived) code: my @nums = 1 .. 1000; sub _remove { if (@nums) { Promise::ES6->resolve(shift @nums)->then(\&_remove); } } _remove(); �� will eventually fail because it will reach Perl�䏭 call stack size limit. That problem probably won�脌 affect most applications. The best way to avoid it, though, is to use asynchronous promises, �� la JavaScript. To do that, first choose one of the following event interfaces: - [IO::Async](https://metacpan.org/pod/IO::Async) - [AnyEvent](https://metacpan.org/pod/AnyEvent) - [Mojo::IOLoop](https://metacpan.org/pod/Mojo::IOLoop) (part of [Mojolicious](https://metacpan.org/pod/Mojolicious)) Then, before you start creating promises, do this: Promise::ES6::use_event('AnyEvent'); �� or: Promise::ES6::use_event('Mojo::IOLoop'); �� or: Promise::ES6::use_event('IO::Async', $loop); That�䏭 it! Promise::ES6 instances will now work asynchronously rather than synchronously. Note that this changes Promise::ES6 _globally_. In IO::Async�䏭 case, it won�脌 increase the passed-in [IO::Async::Loop](https://metacpan.org/pod/IO::Async::Loop) instance�䏭 reference count, but if that loop object goes away, Promise::ES6 won�脌 work until you call `use_event()` again. **IMPORTANT:** For the best long-term scalability and flexibility, your code should work with either synchronous or asynchronous promises. # CANCELLATION Promises have never provided a standardized solution for cancellation�㻳.e., aborting an in-process operation. If you need this functionality, then, you�耱l 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)�䏭 implementation for an example of this. - Implement the cancellation on a request object that your �𦑩romise-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�耱l need to decide if it makes more sense for your application to leave a canceled query in the �𦑩ending�� state or to �𦨴ettle�� (i.e., resolve or reject) it. All things being equal, I feel the first approach is the most intuitive, while the latter ends up being �𦣇leaner��. Of note: [Future](https://metacpan.org/pod/Future) implements native cancellation. # MEMORY LEAKS It�䏭 easy to create inadvertent memory leaks using promises in Perl. Here are a few �𦑩ointers�� (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 �𦧺eak-detect-enabled��, which means that if they survive until their original process�䏭 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�脌 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�䏭 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. �汕拎orks better than: my $promise = Promise::ES6->new( sub { my ($resolve, $reject) = @_; # �� etc. } ); # SEE ALSO If you�胩e 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 my refactor of [AnyEvent::XSPromises](https://metacpan.org/pod/AnyEvent::XSPromises). It�䏭 a lot like this library but implemented mostly in XS for speed. [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. Much of the IO::Async ecosystem assumes (or strongly encourages) its use. CPAN contains a number of other modules that implement promises. I think mine are the nicest :), but YMMV. Enjoy! # LICENSE & COPYRIGHT Copyright 2019-2021 Gasper Software Consulting. This library is licensed under the same terms as Perl itself.