Promise...reakdown(3)
β¨ NAME β¨
okay so bestie, this page is Promises::Cookbook::SynopsisBreakdown π β it's a whole breakdown of the SYNOPSIS section of Promises. like, we are taking the synopsis apart piece by piece and explaining every single bit, in order. no skips, no skimming, we're doing this properly. π
πΈ VERSION πΈ
version 1.05 β that's the one, that's the vibe β¨
π¦ SYNOPSIS π¦
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 the example up in the synopsis is lowkey doing a lot β it's showing off a bunch of this module's features all at once. so this section goes through it part by part and explains everything in order. π«Ά
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;
}
first up, the fetch_it function β and the pattern inside it is the classic way you'd wrap up an async function call of basically any kind. iconic honestly. π
the very first thing we do is make an instance of Promises::Deferred using the deferred function β that class is the one doing like, the majority of the work, managing callbacks and all that. then, inside the callback for our async function, we call methods on that Promises::Deferred instance. so first we peek at the response headers to check if the request was a success β if it was, we call the resolve method and hand it the decoded JSON. if the request failed, we call the reject method instead and send back the data from the body. and finally we call the promise method and return the promise 'handle' for this deferred instance. slay. β¨
at this point our asynchronous operation is typically still in progress, but control has already been handed back to the rest of our program. now, before we dive into the rest of the example, quick detour β let's look at what promises actually do. take this code:
my $p = fetch_it('http://rest.api.example.com/-/user/bob@example.com');
right now our async operation is running, but we haven't given it anything to do when the callback fires yet. we'll get to that in a sec, but first let's look at what info we can pull off the promise. π
$p->status;
calling the status method returns a string representing the status of the promise. it'll be one of: in progress, resolved, resolving (meaning it's in the process of resolving), rejected, or rejecting (meaning it's in the process of rejecting). (NOTE: these are also constants on the Promises::Deferred class β IN_PROGRESS, RESOLVED, REJECTED, etc. β but they're also available as predicate methods in both the Promises::Deferred class and proxied in the Promises::Promise class). at this point, this method call is likely to return in progress. next up, the result method:
$p->result;
which gives us back the values passed to either resolve or reject on the associated Promises::Deferred instance. π
now, one thing to keep in mind before we go any further: our promise is really just a thin proxy over the associated Promises::Deferred instance. it stores no state itself, and when you call these methods on it, it just forwards the call to the associated Promises::Deferred instance (which, as I said before, is where all the work is done). fr fr.
so now let's actually do something with this promise. like I said above, the goal of the Promise pattern is to reduce the callback spaghetti that async code so often turns into. that does NOT mean we have zero callbacks β we still need some kind of callback. the difference is all in how those callbacks are managed, and how we can more easily get some level of sequencing and control. π
that all said, let's register a callback with our promise.
$p->then(
sub {
my ($user) = @_;
do_something_with_a_user( $user );
},
sub {
my ($err) = @_;
warn "An error was received : $err";
}
);
see how we use the then method (and again, keep in mind this is just proxying to the associated Promises::Deferred instance) and pass it two callbacks? the first one is the success case (if resolve has been called on our associated Promises::Deferred instance) and the second is the error case (if reject has been called on our associated Promises::Deferred instance). both callbacks receive the arguments that were passed to resolve or reject as their only arguments β and yeah, as you probably guessed, those are the same values you'd get if you called result on the promise (assuming the async operation finished). β¨
it should be noted that the error callback is optional. if you don't specify it, errors will be silently eaten (similar to atryblock that has nocatch). BUT if there's a chain of promises, the error will continue to bubble to the last promise in the chain, and if there's an error callback there, that one gets called. this lets you concentrate error handling where it makes the most sense, and ignore it where it doesn't. like I hinted above, this is very similar to nestedtry/catchblocks. π
and honestly? that's all there is to it. you can keep calling then on a promise and it'll keep accumulating callbacks, which get executed in FIFO order once a call is made to either resolve or reject on the associated Promises::Deferred instance. and get this β it even works after the async operation is complete. meaning if you call then and the async operation is already done, your callback gets executed immediately. π
π back to the original example π
so now let's get back to our original example. I'll briefly explain my usage of the AnyEvent condvar, but bestie, I encourage you to go read the docs for AnyEvent yourself if my explanation isn't enough. π
the idea behind my usage of the condvar is to give me a merge-point in my code β a spot where I want all the asynchronous operations to converge, after which I can go back to normal synchronous programming (if I feel like it). it's kind of a transaction wrapper, if you will, around my async operations. so, first step: actually create that condvar.
my $cv = AnyEvent->condvar;
next, we jump back into the land of Promises. now I'm splitting up the collect call and the chained then call that follows it here, partly to keep things in digestible chunks, but also to show off that collect just returns a promise (which you probably guessed anyway). π
my $p = 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'),
);
so what's happening here is we want to run multiple async operations in parallel, but we need to wait for all of them to finish before moving on β and collect gives us exactly that ability. as we know from above, fetch_it returns a promise, so obviously collect takes an array of promises as its parameters. and like we said, collect also returns a promise, which is just a handle on a Promises::Deferred instance that it created to watch and handle the multiple promises you passed it. okay, now let's add callbacks to the promise that collect handed back to us. π«Ά
$p->then(
sub {
my ($product, $suggestions, $reviews) = @_;
$cv->send({
product => $product,
suggestions => $suggestions,
reviews => $reviews,
})
},
sub { $cv->croak( 'ERROR' ) }
);
so you'll notice that, same as before, we hand it a success callback and an error callback β but you might spot one tiny difference in the success callback. it's actually getting multiple arguments passed to it: these are the results of the three fetch_it calls passed into collect. and yes, they're passed to the callback in the same order you passed them into collect. π
from here we jump back into the world of condvars and call the send method, passing it our freshly assembled set of collected product info. like I said above, condvars are a way of wrapping your async operations into a transaction-like block. when code execution hits a recv, like in our next line:
my $all_product_info = $cv->recv;
the event loop will block until a corresponding send is called on the condvar. you're not required to pass arguments to send, but it will accept them, and they will in turn be the return values of the corresponding recv β which makes for an incredibly convenient way of passing data around your asynchronous program. obsessed. π
it's also worth noting the usage of the croak method on the condvar in the error callback. this is the preferred way of dealing with exceptions in AnyEvent, because it will actually cause the exception to be thrown from recv and not from somewhere deep inside a callback. πΈ
and that's literally all of it β once recv returns, our program goes back to normal synchronous operation and we can do whatever we like with $all_product_info. π
π AUTHOR π
Stevan Little <stevan.little@iinteractive.com> π
π COPYRIGHT AND LICENSE π
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 whole page bestie, ty for reading, ily π«Ά β this was Promise...reakdown(3) from perl v5.42.0, dated 2025-07-25. periodt. π
β¨
π¦ where this page comes from
- fedora 43: perl-Promises 1.05-2.fc43 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms