1Promises::Cookbook::SynUospesrisCBornetarkidbouwtne(d3P)PreormlisDeosc:u:mCeonotkabtoiookn::SynopsisBreakdown(3)
2
3
4

NAME

6       Promises::Cookbook::SynopsisBreakdown - A breakdown of the SYNOPSIS
7       section of Promises
8

VERSION

10       version 1.04
11

SYNOPSIS

13         use AnyEvent::HTTP;
14         use JSON::XS qw[ decode_json ];
15         use Promises qw[ collect deferred ];
16
17         sub fetch_it {
18             my ($uri) = @_;
19             my $d = deferred;
20             http_get $uri => sub {
21                 my ($body, $headers) = @_;
22                 $headers->{Status} == 200
23                     ? $d->resolve( decode_json( $body ) )
24                     : $d->reject( $body )
25             };
26             $d->promise;
27         }
28
29         my $cv = AnyEvent->condvar;
30
31         collect(
32             fetch_it('http://rest.api.example.com/-/product/12345'),
33             fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
34             fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
35         )->then(
36             sub {
37                 my ($product, $suggestions, $reviews) = @_;
38                 $cv->send({
39                     product     => $product,
40                     suggestions => $suggestions,
41                     reviews     => $reviews,
42                 })
43             },
44             sub { $cv->croak( 'ERROR' ) }
45         );
46
47         my $all_product_info = $cv->recv;
48

DESCRIPTION

50       The example in the synopsis actually demonstrates a number of the
51       features of this module, this section will break down each part and
52       explain them in order.
53
54         sub fetch_it {
55             my ($uri) = @_;
56             my $d = deferred;
57             http_get $uri => sub {
58                 my ($body, $headers) = @_;
59                 $headers->{Status} == 200
60                     ? $d->resolve( decode_json( $body ) )
61                     : $d->reject( $body )
62             };
63             $d->promise;
64         }
65
66       First is the "fetch_it" function, the pattern within this function is
67       the typical way in which you might wrap an async function call of some
68       kind. The first thing we do it to create an instance of
69       Promises::Deferred using the "deferred" function, this is the class
70       which does the majority of the work or managing callbacks and the like.
71       Then within the callback for our async function, we will call methods
72       on the Promises::Deferred instance. In the case we first check the
73       response headers to see if the request was a success, if so, then we
74       call the "resolve" method and pass the decoded JSON to it. If the
75       request failed, we then call the "reject" method and send back the data
76       from the body. Finally we call the "promise" method and return the
77       promise 'handle' for this deferred instance.
78
79       At this point out asynchronous operation will typically be in progress,
80       but control has been returned to the rest of our program. Now, before
81       we dive into the rest of the example, lets take a quick detour to look
82       at what promises do. Take the following code for example:
83
84         my $p = fetch_it('http://rest.api.example.com/-/user/bob@example.com');
85
86       At this point, our async operation is running, but we have not yet
87       given it anything to do when the callback is fired. We will get to that
88       shortly, but first lets look at what information we can get from the
89       promise.
90
91         $p->status;
92
93       Calling the "status" method will return a string representing the
94       status of the promise. This will be either in progress, resolved,
95       resolving (meaning it is in the process of resolving), rejected or
96       rejecting (meaning it is in the process of rejecting).  (NOTE: these
97       are also constants on the Promises::Deferred class, "IN_PROGRESS",
98       "RESOLVED", "REJECTED", etc., but they are also available as predicate
99       methods in both the Promises::Deferred class and proxied in the
100       Promises::Promise class). At this point, this method call is likely to
101       return in progress. Next is the "result" method:
102
103         $p->result;
104
105       which will give us back the values that are passed to either "resolve"
106       or "reject" on the associated Promises::Deferred instance.
107
108       Now, one thing to keep in mind before we go any further is that our
109       promise is really just a thin proxy over the associated
110       Promises::Deferred instance, it stores no state itself, and when these
111       methods are called on it, it simply forwards the call to the associated
112       Promises::Deferred instance (which, as I said before, is where all the
113       work is done).
114
115       So, now, lets actually do something with this promise. So as I said
116       above the goal of the Promise pattern is to reduce the callback
117       spaghetti that is often created with writing async code. This does not
118       mean that we have no callbacks at all, we still need to have some kind
119       of callback, the difference is all in how those callbacks are managed
120       and how we can more easily go about providing some level of sequencing
121       and control.
122
123       That all said, lets register a callback with our promise.
124
125         $p->then(
126             sub {
127                 my ($user) = @_;
128                 do_something_with_a_user( $user );
129             },
130             sub {
131                 my ($err) = @_;
132                 warn "An error was received : $err";
133             }
134         );
135
136       As you can see, we use the "then" method (again, keep in mind this is
137       just proxying to the associated Promises::Deferred instance) and passed
138       it two callbacks, the first is for the success case (if "resolve" has
139       been called on our associated Promises::Deferred instance) and the
140       second is the error case (if "reject" has been called on our associated
141       Promises::Deferred instance). Both of these callbacks will receive the
142       arguments that were passed to "resolve" or "reject" as their only
143       arguments, as you might have guessed, these values are the same values
144       you would get if you called "result" on the promise (assuming the async
145       operation was completed).
146
147       It should be noted that the error callback is optional. If it is not
148       specified then errors will be silently eaten (similar to a "try" block
149       that has not "catch"). If there is a chain of promises however, the
150       error will continue to bubble to the last promise in the chain and if
151       there is an error callback there, it will be called. This allows you to
152       concentrate error handling in the places where it makes the most sense,
153       and ignore it where it doesn't make sense. As I alluded to above, this
154       is very similar to nested "try/catch" blocks.
155
156       And really, that's all there is to it. You can continue to call "then"
157       on a promise and it will continue to accumulate callbacks, which will
158       be executed in FIFO order once a call is made to either "resolve" or
159       "reject" on the associated Promises::Deferred instance. And in fact, it
160       will even work after the async operation is complete. Meaning that if
161       you call "then" and the async operation is already completed, your
162       callback will be executed immediately.
163
164       So, now lets get back to our original example. I will briefly explain
165       my usage of the AnyEvent "condvar", but I encourage you to review the
166       docs for AnyEvent yourself if my explanation is not enough.
167
168       So, the idea behind my usage of the "condvar" is to provide a merge-
169       point in my code at which point I want all the asynchronous operations
170       to converge, after which I can resume normal synchronous programming
171       (if I so choose). It provides a kind of a transaction wrapper if you
172       will, around my async operations. So, first step is to actually create
173       that "condvar".
174
175         my $cv = AnyEvent->condvar;
176
177       Next, we jump back into the land of Promises. Now I am breaking apart
178       the calling of "collect" and the subsequent chained "then" call here to
179       help keep things in digestible chunks, but also to illustrate that
180       "collect" just returns a promise (as you might have guessed anyway).
181
182         my $p = collect(
183             fetch_it('http://rest.api.example.com/-/product/12345'),
184             fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
185             fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
186         );
187
188       So, what is going on here is that we want to be able to run multiple
189       async operations in parallel, but we need to wait for all of them to
190       complete before we can move on, and "collect" gives us that ability.
191       As we know from above, "fetch_it" is returning a promise, so obviously
192       "collect" takes an array of promises as its parameters. As we said
193       before "collect" also returns a promise, which is just a handle on a
194       "Promises::Deferred" instance it created to watch and handle the
195       multiple promises you passed it. Okay, so now lets move onto adding
196       callbacks to our promise that "collect" returned to us.
197
198         $p->then(
199             sub {
200                 my ($product, $suggestions, $reviews) = @_;
201                 $cv->send({
202                     product     => $product,
203                     suggestions => $suggestions,
204                     reviews     => $reviews,
205                 })
206             },
207             sub { $cv->croak( 'ERROR' ) }
208         );
209
210       So, you will notice that, as before, we provide a success and an error
211       callback, but you might notice one slight difference in the success
212       callback. It is actually being passed multiple arguments, these are the
213       results of the three "fetch_it" calls passed into "collect", and yes,
214       they are passed to the callback in the same order you passed them into
215       "collect". So from here we jump back into the world of "condvars", and
216       we call the "send" method and pass it our newly assembled set of
217       collected product info. As I said above, "condvars" are a way of
218       wrapping your async operations into a transaction like block, when code
219       execution encounters a "recv", such as in our next line of code:
220
221         my $all_product_info = $cv->recv;
222
223       the event loop will block until a corresponding "send" is called on the
224       "condvar". While you are not required to pass arguments to "send" it
225       will accept them and the will in turn be the return values of the
226       corresponding "recv", which makes for an incredibly convenient means of
227       passing data around your asynchronous program.
228
229       It is also worth noting the usage of the "croak" method on the
230       "condvar" in the error callback. This is the preferred way of dealing
231       with exceptions in AnyEvent because it will actually cause the
232       exception to be thrown from "recv" and not somewhere deep within a
233       callback.
234
235       And that is all of it, once "recv" returns, our program will go back to
236       normal synchronous operation and we can do whatever it is we like with
237       $all_product_info.
238

AUTHOR

240       Stevan Little <stevan.little@iinteractive.com>
241
243       This software is copyright (c) 2020, 2019, 2017, 2014, 2012 by Infinity
244       Interactive, Inc.
245
246       This is free software; you can redistribute it and/or modify it under
247       the same terms as the Perl 5 programming language system itself.
248
249
250
251perl v5.32.0                      2020-P0r7o-m2i8ses::Cookbook::SynopsisBreakdown(3)
Impressum