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

AUTHOR

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