1Promises::Cookbook::GenUtsleerInCtornot(r3i)buted Perl DPorcoummiesnetsa:t:iCoonokbook::GentleIntro(3)
2
3
4
6 Promises::Cookbook::GentleIntro - All you need to know about Promises
7
9 version 1.04
10
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
209 • Resolved callbacks are like "try" blocks: they can either execute
210 some code successfully or throw an exception.
211
212 • Rejected 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
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)