Promises(3)

πŸ“¦
version 1.05 Β· 2 versions
, in fedora 43

πŸ’– Promises πŸ’–

okay so bestie, this is the Promises(3) page β€” user contributed perl documentation, and we're about to make async perl make sense. πŸ¦‹


🌸 NAME 🌸

Promises β€” an implementation of Promises in Perl. periodt. πŸ’…

✨ VERSION ✨

version 1.05

πŸŽ€ SYNOPSIS πŸŽ€

like, here's the whole vibe in one go:

use AnyEvent::HTTP;
use JSON::XS qw[ decode_json ];
use Promises qw[ collect deferred ];

sub fetch_it {
    my ($uri) = @_;
    my $d = deferred;
    http_get $uri => sub {
        my ($body, $headers) = @_;
        $headers->{Status} == 200
            ? $d->resolve( decode_json( $body ) )
            : $d->reject( $body )
    };
    $d->promise;
}

my $cv = AnyEvent->condvar;

collect(
    fetch_it('http://rest.api.example.com/-/product/12345'),
    fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
    fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
)->then(
    sub {
        my ($product, $suggestions, $reviews) = @_;
        $cv->send({
            product     => $product,
            suggestions => $suggestions,
            reviews     => $reviews,
        })
    },
    sub { $cv->croak( 'ERROR' ) }
);

my $all_product_info = $cv->recv;

πŸ’œ DESCRIPTION πŸ’œ

So this module is an implementation of the "Promise/A+" pattern for asynchronous programming. And the whole point of promises, bestie, is that they're a way to deal with the callback spaghetti that async programs keep producing. No more noodle code. 🍝➑️✨

😭 FUTURE BACKWARDS COMPATIBILITY WARNING 😭

The version of this module is getting bumped up to 0.90 as the first step on the road to 1.0, and the goal there is full Promises/A+ spec compatibility. This is a departure from the previous goal of being compatible with the Promises/A spec, which means behavior may change in subtle ways (they'll try to document this completely and clearly whenever possible, promise 🀞).

It is HIGHLY recommended that you test things very thoroughly before upgrading to this version. like, actually. test it. πŸ™ˆ

πŸ’€ BACKWARDS COMPATIBILITY WARNING πŸ’€

In versions up to and including 0.08 there was a bug in how rejected promises were handled. According to the spec, a rejected callback can:

  • Rethrow the exception, in which case the next rejected handler in the chain would be called, or
  • Handle the exception (by not dieing), in which case the next resolved handler in the chain would be called.

In previous versions of Promises, this last step was handled incorrectly: a rejected handler had no way of handling the exception. Once a promise was rejected, only rejected handlers in the chain would be called. πŸ’€

πŸŒ€ Relation to the various Perl event loops πŸŒ€

This module is actually Event Loop agnostic β€” the SYNOPSIS up there uses AnyEvent::HTTP, but that's just an example, bestie. It can work with any of the existing event loops out on CPAN. Over the next few releases they'll try to add documentation illustrating each of the different event loops and how best to use Promises with them. πŸ‘‘

🧚 Relation to the Promise/A spec 🧚

With some differences, they're following the API spec called "Promise/A" (and the clarification that's called "Promise/A+") which was created by the Node.JS community. For the most part this is the same API implemented in the latest jQuery and in the YUI Deferred plug-in (though some purists argue that they both got it wrong β€” google it if you care πŸ™„). They differ in some respects, mostly because Perl idioms and best practices are not the same as Javascript idioms and best practices.

But the one important difference that should be noted: "Promise/A+" strongly suggests that the callbacks given to then should be run asynchronously (meaning in the next turn of the event loop). They do not do this by default, because doing so would bind them to a given event loop implementation, which they very much want to avoid. However, you can now specify an event loop "backend" when using Promises, and assuming a Deferred backend has been written, it will provide this feature accordingly. ✨

🫢 Using a Deferred backend 🫢

As mentioned above, the default Promises::Deferred class calls the success or error then() callback synchronously, because it isn't tied to a particular event loop. However, it is recommended that you use the appropriate Deferred backend for whichever event loop you are running.

Typically an application uses a single event loop, so all Promises should use the same event-loop. Module implementers should just use the Promises class directly:

package MyClass;
use Promises qw(deferred collect);

End users should specify which Deferred backend they wish to use. For instance if you're using AnyEvent, you can do:

use Promises backend => ['AnyEvent'];
use MyClass;

The Promises returned by MyClass will automatically use whichever event loop AnyEvent is using. slay. πŸ’…

See:

  • Promises::Deferred::AE
  • Promises::Deferred::AnyEvent
  • Promises::Deferred::EV
  • Promises::Deferred::Mojo
  • Promises::Deferred::IO::Async

πŸ¦‹ Relation to Promises/Futures in Scala πŸ¦‹

Scala has a notion of Promises and an associated idea of Futures as well. The differences and similarities between this module and the Promises found in Scalar are highlighted in depth in a cookbook entry below. πŸ‘‡

πŸ“ Cookbook πŸ“

Promises::Cookbook::GentleIntro

Read this first! This cookbook provides a step-by-step explanation of how Promises work and how to use them.

Promises::Cookbook::SynopsisBreakdown

This breaks down the example in the SYNOPSIS and walks through much of the details of Promises and how they work.

Promises::Cookbook::TIMTOWTDI

Promise are just one of many ways to do async programming, this entry takes the Promises SYNOPSIS again and illustrates some counter examples with various modules.

Promises::Cookbook::ChainingAndPipelining

One of the key benefits of Promises is that it retains much of the flow of a synchronous program, this entry illustrates that and compares it with a synchronous (or blocking) version.

Promises::Cookbook::Recursion

This entry explains how to keep the stack under control when using Promises recursively.

Promises::Cookbook::ScalaFuturesComparison

This entry takes some examples of Futures in the Scala language and translates them into Promises. This entry also showcases using Promises with Mojo::UserAgent.


⭐ EXPORTS ⭐

  • "deferred" β€” This just creates an instance of the Promises::Deferred class, it is purely for convenience.

Can take a coderef, which will be dealt with as a "then" argument.

my $promise = deferred sub {
    ... do stuff ...

    return $something;
};

# equivalent to

my $dummy = deferred;

my $promise = $dummy->then(sub {
    ... do stuff ...

    return $something;
});

$dummy->resolve;
  • resolved( @values ) β€” Creates an instance of Promises::Deferred resolved with the provided @values. Purely a shortcut for
my $promise = deferred;
$promise->resolve(@values);
  • rejected( @values ) β€” Creates an instance of Promises::Deferred rejected with the provided @values. Purely a shortcut for
my $promise = deferred;
$promise->reject(@values);
  • collect( @promises ) β€” Accepts a list of Promises::Promise objects and then returns a Promises::Promise object which will be called once all the @promises have completed (either as an error or as a success).

The eventual result of the returned promise object will be an array of all the results of each of the @promises in the order in which they where passed to "collect" originally, wrapped in arrayrefs, or the first error if at least one of the promises fail.

If "collect" is passed a value that is not a promise, it'll be wrapped in an arrayref and passed through.

my $p1 = deferred;
my $p2 = deferred;
$p1->resolve(1);
$p2->resolve(2,3);

collect(
    $p1,
    'not a promise',
    $p2,
)->then(sub{
    print join ' : ', map { join ', ', @$_ } @_; # => "1 : not a promise : 2, 3"
})
  • collect_hash( @promises ) β€” Like "collect", but flatten its returned arrayref into a single hash-friendly list.

"collect_hash" can be useful to a structured hash instead of a long list of promise values.

For example,

my $id = 12345;

collect(
    fetch_it("http://rest.api.example.com/-/product/$id"),
    fetch_it("http://rest.api.example.com/-/product/suggestions?for_sku=$id"),
    fetch_it("http://rest.api.example.com/-/product/reviews?for_sku=$id"),
)->then(
    sub {
        my ($product, $suggestions, $reviews) = @_;
        $cv->send({
            product     => $product,
            suggestions => $suggestions,
            reviews     => $reviews,
            id          => $id
        })
    },
    sub { $cv->croak( 'ERROR' ) }
);

could be rewritten as

my $id = 12345;

collect_hash(
    id          => $id,
    product     => fetch_it("http://rest.api.example.com/-/product/$id"),
    suggestions => fetch_it("http://rest.api.example.com/-/product/suggestions?for_sku=$id"),
    reviews     => fetch_it("http://rest.api.example.com/-/product/reviews?for_sku=$id"),
)->then(
    sub {
        my %results = @_;
        $cv->send(\%results);
    },
    sub { $cv->croak( 'ERROR' ) }
);

Note that all promise values of the key/value pairs passed to "collect_hash" must return a scalar or nothing, as returning more than one value would mess up the returned hash format. If a promise does return more than one value, "collect_hash" will consider it as having failed.

If you know that a promise can return more than one value, you can do:

my $collected = collect_hash(
    this => $promise_returning_scalar,
    that => $promise_returning_list->then(sub{ [ @_ ] } ),
);

okay bestie that's part 1 down β€” part 2 has the rest, don't go anywhere 🌈

✨ SEE ALSO ✨

okay bestie, if you wanna go down the rabbit hole and read the ~lore~, here's the reading list πŸ’–

πŸ’– Promises in General πŸ’–


🌸 Perl Alternatives 🌸

  • Future β€” another Perl promise-ish module πŸ‘‘
  • Mojo::Promise β€” Part of the Mojolicious package. πŸ¦‹
  • Promise::ES6 β€” ES6-style promises in Perl ✨
  • Promise::Tiny β€” tiny promise energy πŸŽ€
  • AnyEvent::XSPromises β€” promises for the AnyEvent girlies πŸ’…
  • Promise::XS β€” XS-speed promises, no cap πŸ”₯

πŸ‘‘ AUTHOR πŸ‘‘

This whole thing was written by Stevan Little β€” Stevan Little <stevan.little@iinteractive.com> 🫢


This software is copyright (c) 2025, 2017, 2014, 2012 by Infinity Interactive, Inc. πŸ’–

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. 🌈


and that's the page, bestie!! this is from perl v5.42.0, dated 2025-07-25, and it's the Promises(3) manual page. periodt πŸ’…βœ¨

πŸ“¦ where this page comes from