package Promises::Cookbook::SynopsisBreakdown;
__END__
=pod
=head1 NAME
Promises::Cookbook::SynopsisBreakdown
=head1 VERSION
version 0.03
=head1 SYNOPSIS
use AnyEvent::HTTP;
use JSON::XS qw[ decode_json ];
use Promises qw[ when 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;
when(
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;
=head1 DESCRIPTION
The example in the synopsis actually demonstrates a number of the
features of this module, this section will break down each part
and explain them 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 is the C function, the pattern within this function
is the typical way in which you might wrap an async function call
of some kind. The first thing we do it to create an instance of
L using the C function, this is the
class which does the majority of the work or managing callbacks
and the like. Then within the callback for our async function,
we will call methods on the L instance. In the
case we first check the response headers to see if the request was
a success, if so, then we call the C method and pass the
decoded JSON to it. If the request failed, we then call the C
method and send back the data from the body. Finally we call the
C method and return the promise 'handle' for this deferred
instance.
At this point out asynchronous operation will typically be in
progress, but control has been returned to the rest of our
program. Now, before we dive into the rest of the example, lets
take a quick detour to look at what promises do. Take the following
code for example:
my $p = fetch_it('http://rest.api.example.com/-/user/bob@example.com');
At this point, our async operation is running, but we have not yet
given it anything to do when the callback is fired. We will get to
that shortly, but first lets look at what information we can get
from the promise.
$p->status;
Calling the C method will return a string representing the
status of the promise. This will be either I, I,
I (meaning it is in the process of resolving), I
or I (meaning it is in the process of rejecting).
(NOTE: these are also constants on the L class,
C, C, C, etc., but they are also
available as predicate methods in both the L class
and proxied in the L class). At this point, this
method call is likely to return I. Next is the C
method:
$p->result;
which will give us back the values that are passed to either C
or C on the associated L instance.
Now, one thing to keep in mind before we go any further is that our
promise is really just a thin proxy over the associated L
instance, it stores no state itself, and when these methods are called on
it, it simply forwards the call to the associated L
instance (which, as I said before, is where all the work is done).
So, now, lets actually do something with this promise. So as I said above
the goal of the Promise pattern is to reduce the callback spaghetti that
is often created with writing async code. This does not mean that we have
no callbacks at all, we still need to have some kind of callback, the
difference is all in how those callbacks are managed and how we can more
easily go about providing some level of sequencing and control.
That all said, lets 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";
}
);
As you can see, we use the C method (again, keep in mind this is
just proxying to the associated L instance) and
passed it two callbacks, the first is for the success case (if C
has been called on our associated L instance) and
the second is the error case (if C has been called on our
associated L instance). Both of these callbacks will
receive the arguments that were passed to C or C as
their only arguments, as you might have guessed, these values are the
same values you would get if you called C on the promise
(assuming the async operation was completed).
It should be noted that the error callback is optional. If it is not
specified then errors will be silently eaten (similar to a C block
that has not C). If there is a chain of promises however, the
error will continue to bubble to the last promise in the chain and
if there is an error callback there, it will be called. This allows
you to concentrate error handling in the places where it makes the most
sense, and ignore it where it doesn't make sense. As I alluded to above,
this is very similar to nested C blocks.
And really, thats all there is to it. You can continue to call C
on a promise and it will continue to accumulate callbacks, which will
be executed in in FIFO order once a call is made to either C
or C on the associated L instance. And in
fact, it will even work after the async operation is complete. Meaning
that if you call C and the async operation is already completed,
your callback will be executed immediately.
So, now lets get back to our original example. I will briefly explain
my usage of the L C, but I encourage you to review
the docs for L yourself if my explanation is not enough.
So, the idea behind my usage of the C is to provide a
merge-point in my code at which point I want all the asynchronous
operations to converge, after which I can resume normal synchronous
programming (if I so choose). It provides a kind of a transaction
wrapper if you will, around my async operations. So, first step is
to actually create that C.
my $cv = AnyEvent->condvar;
Next, we jump back into the land of Promises. Now I am breaking apart
the calling of C and the subsequent chained C call here
to help keep things in digestible chunks, but also to illustrate that
C just returns a promise (as you might have guessed anyway).
my $p = when(
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 is going on here is that we want to be able to run multiple
async operations in parallel, but we need to wait for all of them to
complete before we can move on, and C gives us that ability.
As we know from above, C is returning a promise, so obviously
C takes an array of promises as its parameters. As we said before
C also returns a promise, which is just a handle on a
C instance it created to watch and handle the
multiple promises you passed it. Okay, so now lets move onto adding
callbacks to our promise that C returned to us.
$p->then(
sub {
my ($product, $suggestions, $reviews) = @_;
$cv->send({
product => $product,
suggestions => $suggestions,
reviews => $reviews,
})
},
sub { $cv->croak( 'ERROR' ) }
);
So, you will notice that, as before, we provide a success and an error
callback, but you might notice one slight difference in the success
callback. It is actually being passed multiple arguments, these are
the results of the three C calls passed into C, and yes,
they are passed to the callback in the same order you passed them into
C. So from here we jump back into the world of C, and
we call the C method and pass it our newly assembled set of
collected product info. As I said above, C are a way of
wrapping your async operations into a transaction like block, when
code execution encounters a C, such as in our next line of code:
my $all_product_info = $cv->recv;
the event loop will block until a corresponding C is called on
the C. While you are not required to pass arguments to C
it will accept them and the will in turn be the return values of
the corresponding C, which makes for an incredibly convenient
means of passing data around your asynchronous program.
It is also worth noting the usage of the C method on the
C in the error callback. This is the preferred way of
dealing with exceptions in L because it will actually
cause the exception to be thrown from C and not somewhere
deep within a callback.
And that is all of it, once C returns, our program will go
back to normal synchronous operation and we can do whatever it is
we like with C.
=head1 NAME
Promises::Cookbook::SynopsisBreakdown - A breakdown of the SYNOPSIS section of Promises
=head1 AUTHOR
Stevan Little
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 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.
=cut