1Promises::Cookbook::GenUtsleerInCtornot(r3i)buted Perl DPorcoummiesnetsa:t:iCoonokbook::GentleIntro(3)
2
3
4

NAME

6       Promises::Cookbook::GentleIntro - All you need to know about Promises
7

VERSION

9       version 1.04
10

All you need to know about Promises

12       If you have ever done any async programming, you will be familiar with
13       "callback hell", where one callback calls another, calls another, calls
14       another... Promises give us back a top-to-bottom coding style, making
15       async code easier to manage and understand. It looks like synchronous
16       code, but execution is asynchronous.
17
18       The Promises module is event loop agnostic - it can be used with any
19       event loop. Backends exist for AnyEvent (and thus all the event loops
20       supported by AnyEvent) and Mojo::IOLoop.  But more of this later in
21       "Integration with event loops".
22
23       There are two moving parts:
24
25       Deferred objects
26           Deferred objects provide the interface to a specific async request.
27           They execute some asynchronous action and return a promise.
28
29       Promise objects
30           A promise is like a placeholder for a future result.  The promise
31           will either be resolved in case of success, or rejected in case of
32           failure. Promises can be chained together, and each step in the
33           chain is executed sequentially.
34
35       The easiest way to understand how Deferred and Promise objects work is
36       by example.
37
38   Deferred objects
39       A deferred object is used to signal the success or failure of some
40       async action which can be implemented in the async library of your
41       choice.  For instance:
42
43           use Promises qw(deferred);
44           use AnyEvent::HTTP qw(http_get);
45           use JSON qw(decode_json);
46
47           sub fetch_it {
48               my ($uri) = @_;
49               my $deferred = deferred;
50               http_get $uri => sub {
51                   my ($body, $headers) = @_;
52                   $headers->{Status} == 200
53                       ? $deferred->resolve( decode_json($body) )
54                       : $deferred->reject( $headers->{Reason} )
55               };
56               $deferred->promise;
57           }
58
59       The above code makes an asynchronous "http_get" request to the
60       specified $uri. The result of the request at the time the subroutine
61       returns is like Schrödinger's cat: both dead and alive.  In the future
62       it may succeed or it may fail.
63
64       This sub creates a Promises::Deferred object using "deferred", which is
65       either:
66
67       •   resolved on success, in which case it returns the request "body",
68           or
69
70       •   rejected on failure, in which case it returns the reason for
71           failure.
72
73       As a final step, the deferred object returns a Promises::Promise object
74       which represents the future result.
75
76       That's all there is to know about Promises::Deferred.
77
78   Promise objects
79       Promises are a lot like "try"/"catch"/"finally" blocks except that they
80       can be chained together. The most important part of a promise is the
81       "then()" method:
82
83           $promise->then(
84               sub { success! },
85               sub { failure }
86           );
87
88       The "then()" method takes two arguments: a success callback and a
89       failure callback.  But the important part is that it returns a new
90       promise, which is the thing that allows promises to be chained
91       together.
92
93       The simple genius of promises (and I can say that because I didn't
94       invent them) will not be immediately obvious, but bear with me.
95       Promises are very simple, as long as you understand the execution flow:
96
97       Resolving or rejecting a Promise
98
99           use Promises qw(deferred);
100
101           my $deferred = deferred;
102           $deferred->promise->then(
103               sub { say "OK! We received: ".shift(@_)},       # on resolve
104               sub { say "Bah! We failed with: ". shift(@_)}   # on reject
105           );
106
107       What this code does depends on what happens to the $deferred object:
108
109           $deferred->resolve('Yay!');
110           # prints: "OK! We received: Yay!"
111
112           $deferred->reject('Pooh!');
113           # prints "Bah! We failed with: Pooh!"
114
115       A Deferred object can only be resolved or rejected once.  Once it is
116       resolved or rejected, it informs all its promises of the outcome.
117
118       Chaining resolve callbacks
119
120       As mentioned earlier, the "then()" method returns a new promise which
121       will be resolved or rejected in turn. Each "resolve" callback will
122       receive the return value of the previous "resolve" callback:
123
124           deferred
125           ->resolve('red','green')
126           ->promise
127
128           ->then(sub {
129               # @_ contains ('red','green')
130               return ('foo','bar');
131           })
132
133           ->then(sub {
134               # @_ contains ('foo,bar');
135               return 10;
136           })
137
138           ->then( sub {
139               # @_ contains (10)
140           });
141
142       All of these example callbacks have just returned a simple value (or
143       values), so execution has moved from one callback to the next.
144
145       Chaining reject callbacks
146
147       Note that in the above example, in each call to "then()" we specified
148       only a resolved callback, not a rejected callback.  If a promise is
149       resolved or rejected, the action gets passed down the chain until it
150       finds a resolved or rejected handler.  This means that errors can be
151       handled in the appropriate place in the chain:
152
153           my $deferred = deferred;
154
155           $deferred->promise
156           ->then(
157               sub {
158                   my $count = shift();
159                   say "Count: $count";
160                   return $count+1;
161               }
162           )
163           ->then(
164               sub {
165                   my $count = shift();
166                   say "Count: $count";
167                   return $count+1;
168               }
169           )->then(
170               sub {
171                   my $count = shift();
172                   say "Final count: $count";
173                   return $count+1;
174               },
175               sub {
176                   my $reason = shift;
177                   warn "Failed to count: $reason"
178               }
179           );
180
181       If the $deferred object is resolved, it will call each resolved
182       callback in turn:
183
184           $deferred->resolve(5);
185           # prints:
186           #   Count: 5
187           #   Count: 6
188           #   Final count: 7
189
190       If the $deferred object is rejected, however, it will skip all of the
191       steps in the chain until it hits the first rejected callback:
192
193           $deferred->reject('Poor example');
194           # warns:
195           #    "Failed to count: Poor example"
196
197       Important: Event loops do not like fatal exceptions! For this reason
198       the resolved and rejected callbacks are run in "eval" blocks.
199       Exceptions thrown in either type of callback are passed down the chain
200       to the next rejected handler.  If there are no more rejected handlers,
201       then the error is silently swallowed.
202
203       Throwing and handling exceptions
204
205       While you can signal success or failure by calling "resolve()" or
206       "reject()" on the $deferred object, you can also signal success or
207       failure in each step of the promises chain.
208
209Resolved callbacks are like "try" blocks: they can either execute
210           some code successfully or throw an exception.
211
212Rejected callbacks are like "catch" blocks: they can either handle
213           the exception or rethrow it.
214
215           $deferred = deferred;
216
217           $deferred->promise
218           ->then(
219               sub {
220                   my $count = shift;
221                   die "Count too high!" if $count > 100;
222                   return $count
223               }
224           )->then(
225               sub {
226                   say "The count is OK. Continuing";
227                   return @_
228               },
229               sub {
230                   my $error = shift;
231                   warn "We have a problem: $error";
232                   die $error;
233               }
234           )->then(
235               undef,  # no resolved handler
236               sub { return 1; }
237           )-> then(
238               sub {
239                   my $count = shift;
240                   say "Got count: $count";
241               }
242           )
243
244       There are a few ways this code can execute. We can resolve the
245       $deferred object with a reasonable count:
246
247           $deferred->resolve(5);
248           # prints:
249           #   The count is OK. Continuing
250           #   Got count: 5
251
252           $defer
253
254       If we reject the $deferred object, the first rejected handler is
255       called.  It warns, then rethrows the exception with "die" which calls
256       the next rejected handler.  This handler resolves the exception (that
257       is, it doesn't call "die") and returns a value which gets passed to the
258       next resolved handler:
259
260           $deferred->reject('For example purposes')
261           # warns:
262           #    We have a problem: For example purposes
263           # prints:
264           #    Got count: 1
265
266       Finally, if we resolve the $deferred object with a too large count, the
267       first resolved handler throws an exception, which calls the next
268       rejected handler:
269
270           $deferred->resolve(1000);
271           # warns:
272           #    We have a problem: Count too high!
273           # prints:
274           #    Got count: 1
275
276       "catch()"
277
278       In the above example, we called "then()" with "undef" instead of a
279       resolved callback. This could be rewritten to look a bit cleaner using
280       the "catch()" method, which takes just a rejected callback.
281
282           # these two lines are equivalent:
283           $promise->then( undef, sub { rejected cb} )
284           $promise->catch( sub { rejected cb } )
285
286       "finally()"
287
288       Any "try"/"catch" implementation has a "finally" block, which can be
289       used to clean up resources regardless of whether the code in the "try"
290       block succeeded or failed. Promises offer this functionality too.
291
292       The "finally()" method accepts a single callback which is called
293       regardless of whether the previous step was resolved or rejected. The
294       return value (or any exception thrown in the callback) are thrown away,
295       and the chain continues as if it were not there:
296
297           $deferred = deferred;
298           $deferred->promise
299           ->then(
300               sub {
301                   my $count = shift;
302                   if ($count > 10) { die "Count too high"}
303                   return $count
304               }
305           )->finally(
306               sub { say "Finally got: ".shift(@_) }
307           )->then(
308               sub { say "OK: ". shift(@_)   },
309               sub { say "Bah!: ". shift(@_) }
310           );
311
312       If we resolve the $deferred object with a good count, we see:
313
314           $d->resolve(5);
315           # prints:
316           #   Finally got: 5
317           #   OK: 5
318
319       With a high count we get:
320
321           $d->resolve(20);
322           # prints:
323           #   Finally got: Count to high
324           #   Bah: 20
325
326       Chaining async callbacks
327
328       This is where the magic starts: each resolved/rejected handler can not
329       only return a value (or values), it can also return a new Promise.
330       Remember that a Promise represents a future value, which means that
331       execution of the chain will stop until the new Promise has been either
332       resolved or rejected!
333
334       For instance, we could write the following code using the "fetch_it()"
335       sub (see  "Deferred objects") which returns a promise:
336
337           fetch_it('http://domain.com/user/123')
338           ->then(
339               sub {
340                   my $user = shift;
341                   say "User name: ".$user->{name};
342                   say "Fetching total comments";
343                   return fetch_id($user->{total_comments_url});
344               }
345           )->then(
346               sub {
347                   my $total = shift;
348                   say "User has left $total comments"
349               }
350           )
351           ->catch(
352               sub {
353                   warn @_
354               }
355           );
356
357       This code sends an asynchronous request to get the page for user 123
358       and returns a promise. Once the promise is resolved, it sends an
359       asynchronous request to get the total comments for that user and again
360       returns a promise.  Once the second promise is resolved, it prints out
361       the total number of comments. If either promise were to be rejected, it
362       would skip down the chain looking for the first rejected handler and
363       execute that.
364
365       This is organised to look like synchronous code.  Each step is executed
366       sequentially, it is easy to read and easy to understand, but it works
367       asynchronously.  While we are waiting for a response from "domain.com"
368       (while our promise remains unfulfilled), the event loop can happily
369       continue running code elsewhere in the application.
370
371       In fact, it's not just Promises::Promise objects that can be returned,
372       it can be any object that is ``thenable'' (ie it has a "then()"
373       method). So if you want to integrate your Promises code with a library
374       which is using Future objects, you should be able to do it.
375
376       Running async requests in parallel
377
378       Sometimes order doesn't matter: perhaps we want to retrieve several web
379       pages at the same time.  For that we can use the "collect" helper:
380
381           use Promises qw(collect);
382
383           collect(
384               fetch_it('http://rest.api.example.com/-/product/12345'),
385               fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
386               fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
387           )->then(
388               sub {
389                   my ($product, $suggestions, $reviews) = @_;
390                   # do something with these values
391               },
392               sub { warn @_ }
393           );
394
395       "collect()" accepts a list of promises and returns a new promise (which
396       we'll call $p for clarification purposes.  When all of its promises
397       have been resolved, it resolves $p with the values returned by every
398       promise, in the same order as they were passed in to "collect()".
399
400       Note: Each promise can return multiple values, so $product,
401       $suggestions and $reviews in the example above will all be array refs.
402
403       If any of the passed in promises is rejected, then $p will also be
404       rejected with the reason for the failure.  $p can only be rejected
405       once, so we wil only find out about the first failure.
406
407   Integration with event loops
408       In order to run asynchronous code, you need to run some event loop.
409       That can be as simple as using "CONDITION VARIABLES" in AnyEvent to run
410       the event loop just until a particular condition is met:
411
412           use AnyEvent;
413
414           my $cv = AnyEvent->condvar;
415           collect(
416               fetch_it('http://rest.api.example.com/-/product/12345'),
417               fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
418               fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
419           )->then(
420               sub {
421                   my ($product, $suggestions, $reviews) = @_;
422                   $cv->send({
423                       product     => $product->[0],
424                       suggestions => $suggestions->[0],
425                       reviews     => $reviews->[0],
426                   })
427               },
428               sub { $cv->croak( 'ERROR' ) }
429           );
430
431           # wait for $cv->send or $cv->croak
432           my $results = $cv->recv;
433
434       More usually though, a whole application is intended to be
435       asynchronous, in which case the event loop just runs continuously.
436       Normally you would only need to use $cv's or the equivalent at the
437       point where your application uses a specific async library, as
438       explained in "Deferred objects". The rest of your code can deal purely
439       with Promises.
440
441       Event loop specific backends
442
443       The resolved and rejected callbacks should be run by the event loop,
444       rather than having one callback call the next, which calls the next
445       etc.
446
447       In other words, if a promise is resolved, it doesn't call the resolved
448       callback directly. Instead it adds it to the event loop's queue, then
449       returns immediately. The next time the event loop checks its queue,
450       it'll find the callback in the queue and will call it.
451
452       By default, Promises is event loop agnostic, which means that it
453       doesn't know which event loop to use and so each callback ends up
454       calling the next, etc.  If you're writing Promises-based modules for
455       CPAN, then your code should also be event loop agnostic, in which case
456       you want to use Promises like this:
457
458           use Promises qw(deferred collect);
459
460       However, if you are an end user, then you should specify which event
461       loop you are using at the start of your application:
462
463           use Promises backend => ['AnyEvent']; # or "EV" or "Mojo"
464
465       You only need to specify the backend once - any code in the application
466       which uses Promises will automatically use the specified backend.
467
468   Recursing safely with with "done()"
469       One of the cool things about working with promises is that the return
470       value gets passed down the chain as if the code were synchronous.
471       However that is not always what we want.
472
473       Imagine that we want to process every line in a file, which could be
474       millions of lines. We don't care about the results from each line, all
475       we care about is whether the whole file was processed successfully, or
476       whether something failed.
477
478       In sync code we'd write something like this:
479
480           sub process_file {
481               my $fh = shift;
482               while (my $line = <$fh>) {
483                   process_line($line)
484                       || die "Failed"
485               }
486           }
487
488       Now imagine that "process_line()" runs asynchronously and returns a
489       promise.  By the time it returns, it probably hasn't executed anything
490       yet.  We can't go ahead and read the next line of the file otherwise we
491       could generate a billion promises before any of them has had time to
492       execute.
493
494       Instead, we need to wait for "process_line()" to complete and only then
495       move on to reading the next line.  We could do this as follows:
496
497           # WARNING: EXAMPLE OF INCORRECT CODE #
498
499           use Promises qw(deferred);
500
501           sub process_file {
502               my $fh        = shift;
503               my $deferred  = deferred;
504               my $processor = sub {
505                   my $line = <$fh>;
506                   unless (defined $line) {
507                       # we're done
508                       return $deferred->resolve;
509                   }
510                   process_line($line)->then(
511
512                       # on success, call $processor again
513                       __SUB__,
514
515                       # on failure:
516                       sub {
517                           return $deferred->reject("Failed")
518                       }
519                   )
520               }
521
522               # start the loop
523               $processor->();
524
525               return $deferred->promise
526           }
527
528       This code has two stack problems. The first is that, every time we
529       process a line, we recurse into the current "__SUB__" from the current
530       sub.  This problem is solved by specifying one of the "Event loop
531       specific backends" somewhere in our application, which we discussed
532       above.
533
534       The second problem is that every time we recurse into the current
535       "__SUB__" we're waiting for the return value. Other languages use the
536       Tail Call optimization <http://en.wikipedia.org/wiki/Tail_call> to keep
537       the return stack flat, but we don't have this option.
538
539       Instead, we have the "done()" method which, like "then()", accepts a
540       resolved callback and a rejected callback. But it differs from "then()"
541       in two ways:
542
543       •   It doesn't return a promise, which means that the chain ends with
544           the "done()" step.
545
546       •   Callbacks are not run in an "eval" block, so calling "die()" will
547           throw a fatal exception. (Most event loops, however will catch the
548           exception, warn, and continue running.)
549
550       The code can be rewritten using "done()" instead of "then()" and an
551       event loop specific backend, and it will happily process millions of
552       lines without memory leaks or stack oveflows:
553
554           use Promises backend => ['EV'], 'deferred';
555
556           sub process_file {
557               my $fh        = shift;
558               my $deferred  = deferred;
559               my $processor = sub {
560                   my $line = <$fh>;
561                   unless (defined $line) {
562                       # we're done
563                       return $deferred->resolve;
564                   }
565                   #### USE done() TO END THE CHAIN ####
566                   process_line($line)->done(
567
568                       # on success, call $processor again
569                       __SUB__,
570
571                       # on failure:
572                       sub {
573                           return $deferred->reject("Failed")
574                       }
575                   )
576               }
577
578               # start the loop
579               $processor->();
580
581               return $deferred->promise
582           }
583

AUTHOR

585       Stevan Little <stevan.little@iinteractive.com>
586
588       This software is copyright (c) 2020, 2019, 2017, 2014, 2012 by Infinity
589       Interactive, Inc.
590
591       This is free software; you can redistribute it and/or modify it under
592       the same terms as the Perl 5 programming language system itself.
593
594
595
596perl v5.34.0                      2021-07-22Promises::Cookbook::GentleIntro(3)
Impressum