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 0.99
10
12 If you have every 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 if ( $count > 100 ) {
222 die "Count too high!"
223 }
224 return $count
225 }
226 )->then(
227 sub {
228 say "The count is OK. Continuing";
229 return @_
230 },
231 sub {
232 my $error = shift;
233 warn "We have a problem: $error";
234 die $error;
235 }
236 )
237 )->then(
238 undef, # no resolved handler
239 sub {
240 return 1;
241 }
242 )-> then(
243 sub {
244 my $count = shift;
245 say "Got count: $count";
246 }
247 )
248
249 There are a few ways this code can execute. We can resolve the
250 $deferred object with a reasonable count:
251
252 $deferred->resolve(5);
253 # prints:
254 # The count is OK. Continuing
255 # Got count: 5
256
257 $defer
258
259 If we reject the $deferred object, the first rejected handler is
260 called. It warns, then rethrows the exception with "die" which calls
261 the next rejected handler. This handler resolves the exception (that
262 is, it doesn't call "die") and returns a value which gets passed to the
263 next resolved handler:
264
265 $deferred->reject('For example purposes')
266 # warns:
267 # We have a problem: For example purposes
268 # prints:
269 # Got count: 1
270
271 Finally, if we resolve the $deferred object with a too large count, the
272 first resolved handler throws an exception, which calls the next
273 rejected handler:
274
275 $deferred->resolve(1000);
276 # warns:
277 # We have a problem: Count too high!
278 # prints:
279 # Got count: 1
280
281 "catch()"
282
283 In the above example, we called "then()" with "undef" instead of a
284 resolved callback. This could be rewritten to look a bit cleaner using
285 the "catch()" method, which takes just a rejected callback.
286
287 # these two lines are equivalent:
288 $promise->then( undef, sub { rejected cb} )
289 $promise->catch( sub { rejected cb } )
290
291 "finally()"
292
293 Any "try"/"catch" implementation has a "finally" block, which can be
294 used to clean up resources regardless of whether the code in the "try"
295 block succeeded or failed. Promises offer this functionality too.
296
297 The "finally()" method accepts a single callback which is called
298 regardless of whether the previous step was resolved or rejected. The
299 return value (or any exception thrown in the callback) are thrown away,
300 and the chain continues as if it were not there:
301
302 $deferred = deferred;
303 $deferred->promise
304 ->then(
305 sub {
306 my $count = shift;
307 if ($count > 10) { die "Count too high"}
308 return $count
309 }
310 )->finally(
311 sub { say "Finally got: ".shift(@_) }
312 )->then(
313 sub { say "OK: ". shift(@_) },
314 sub { say "Bah!: ". shift(@_) }
315 );
316
317 If we resolve the $deferred object with a good count, we see:
318
319 $d->resolve(5);
320 # prints:
321 # Finally got: 5
322 # OK: 5
323
324 With a high count we get:
325
326 $d->resolve(20);
327 # prints:
328 # Finally got: Count to high
329 # Bah: 20
330
331 Chaining async callbacks
332
333 This is where the magic starts: each resolved/rejected handler can not
334 only return a value (or values), it can also return a new Promise.
335 Remember that a Promise represents a future value, which means that
336 execution of the chain will stop until the new Promise has been either
337 resolved or rejected!
338
339 For instance, we could write the following code using the "fetch_it()"
340 sub (see "Deferred objects") which returns a promise:
341
342 fetch_it('http://domain.com/user/123')
343 ->then(
344 sub {
345 my $user = shift;
346 say "User name: ".$user->{name};
347 say "Fetching total comments";
348 return fetch_id($user->{total_comments_url});
349 }
350 )->then(
351 sub {
352 my $total = shift;
353 say "User has left $total comments"
354 }
355 )
356 ->catch(
357 sub {
358 warn @_
359 }
360 );
361
362 This code sends an asynchronous request to get the page for user 123
363 and returns a promise. Once the promise is resolved, it sends an
364 asynchronous request to get the total comments for that user and again
365 returns a promise. Once the second promise is resolved, it prints out
366 the total number of comments. If either promise were to be rejected, it
367 would skip down the chain looking for the first rejected handler and
368 execute that.
369
370 This is organised to look like synchronous code. Each step is executed
371 sequentially, it is easy to read and easy to understand, but it works
372 asynchronously. While we are waiting for a response from "domain.com"
373 (while our promise remains unfulfilled), the event loop can happily
374 continue running code elsewhere in the application.
375
376 In fact, it's not just Promises::Promise objects that can be returned,
377 it can be any object that is ``thenable'' (ie it has a "then()"
378 method). So if you want to integrate your Promises code with a library
379 which is using Future objects, you should be able to do it.
380
381 Running async requests in parallel
382
383 Sometimes order doesn't matter: perhaps we want to retrieve several web
384 pages at the same time. For that we can use the "collect" helper:
385
386 use Promises qw(collect);
387
388 collect(
389 fetch_it('http://rest.api.example.com/-/product/12345'),
390 fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
391 fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
392 )->then(
393 sub {
394 my ($product, $suggestions, $reviews) = @_;
395 # do something with these values
396 },
397 sub { warn @_ }
398 );
399
400 "collect()" accepts a list of promises and returns a new promise (which
401 we'll call $p for clarification purposes. When all of its promises
402 have been resolved, it resolves $p with the values returned by every
403 promise, in the same order as they were passed in to "collect()".
404
405 Note: Each promise can return multiple values, so $product,
406 $suggestions and $reviews in the example above will all be array refs.
407
408 If any of the passed in promises is rejected, then $p will also be
409 rejected with the reason for the failure. $p can only be rejected
410 once, so we wil only find out about the first failure.
411
412 Integration with event loops
413 In order to run asynchronous code, you need to run some event loop.
414 That can be as simple as using "CONDITION VARIABLES" in AnyEvent to run
415 the event loop just until a particular condition is met:
416
417 use AnyEvent;
418
419 my $cv = AnyEvent->condvar;
420 collect(
421 fetch_it('http://rest.api.example.com/-/product/12345'),
422 fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
423 fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
424 )->then(
425 sub {
426 my ($product, $suggestions, $reviews) = @_;
427 $cv->send({
428 product => $product->[0],
429 suggestions => $suggestions->[0],
430 reviews => $reviews->[0],
431 })
432 },
433 sub { $cv->croak( 'ERROR' ) }
434 );
435
436 # wait for $cv->send or $cv->croak
437 my $results = $cv->recv;
438
439 More usually though, a whole application is intended to be
440 asynchronous, in which case the event loop just runs continuously.
441 Normally you would only need to use $cv's or the equivalent at the
442 point where your application uses a specific async library, as
443 explained in "Deferred objects". The rest of your code can deal purely
444 with Promises.
445
446 Event loop specific backends
447
448 The resolved and rejected callbacks should be run by the event loop,
449 rather than having one callback call the next, which calls the next
450 etc.
451
452 In other words, if a promise is resolved, it doesn't call the resolved
453 callback directly. Instead it adds it to the event loop's queue, then
454 returns immediately. The next time the event loop checks its queue,
455 it'll find the callback in the queue and will call it.
456
457 By default, Promises is event loop agnostic, which means that it
458 doesn't know which event loop to use and so each callback ends up
459 calling the next, etc. If you're writing Promises-based modules for
460 CPAN, then your code should also be event loop agnostic, in which case
461 you want to use Promises like this:
462
463 use Promises qw(deferred collect);
464
465 However, if you are an end user, then you should specify which event
466 loop you are using at the start of your application:
467
468 use Promises backend => ['AnyEvent']; # or "EV" or "Mojo"
469
470 You only need to specify the backend once - any code in the application
471 which uses Promises will automatically use the specified backend.
472
473 Recursing safely with with "done()"
474 One of the cool things about working with promises is that the return
475 value gets passed down the chain as if we the code were synchronous.
476 However that is not always what we want.
477
478 Imagine that we want to process every line in a file, which could be
479 millions of lines. We don't care about the results from each line, all
480 we care about is whether the whole file was processed successfully, or
481 whether something failed.
482
483 In sync code we'd write something like this:
484
485 sub process_file {
486 my $fh = shift;
487 while (my $line = <$fh>) {
488 process_line($line)
489 || die "Failed"
490 }
491 }
492
493 Now imagine that "process_line()" runs asynchronously and returns a
494 promise. By the time it returns, it probably hasn't executed anything
495 yet. We can't go ahead and read the next line of the file otherwise we
496 could generate a billion promises before any of them has had time to
497 execute.
498
499 Instead, we need to wait for "process_line()" to complete and only then
500 move on to reading the next line. We could do this as follows:
501
502 # WARNING: EXAMPLE OF INCORRECT CODE #
503
504 use Promises qw(deferred);
505
506 sub process_file {
507 my $fh = shift;
508 my $deferred = deferred;
509 my $processor = sub {
510 my $line = <$fh>;
511 unless (defined $line) {
512 # we're done
513 return $deferred->resolve;
514 }
515 process_line($line)->then(
516
517 # on success, call $processor again
518 __SUB__,
519
520 # on failure:
521 sub {
522 return $deferred->reject("Failed")
523 }
524 )
525 }
526
527 # start the loop
528 $processor->();
529
530 return $deferred->promise
531 }
532
533 This code has two stack problems. The first is that, every time we
534 process a line, we recurse into the current "__SUB__" from the current
535 sub. This problem is solved by specifying an "Event loop specific
536 backend" somewhere in our application, which we discussed above.
537
538 The second problem is that every time we recurse into the current
539 "__SUB__" we're waiting for the return value. Other languages use the
540 Tail Call optimization <http://en.wikipedia.org/wiki/Tail_call> to keep
541 the return stack flat, but we don't have this option.
542
543 Instead, we have the "done()" method which, like "then()", accepts a
544 resolved callback and a rejected callback. But it differs from "then()"
545 in two ways:
546
547 · It doesn't return a promise, which means that the chain ends with
548 the "done()" step.
549
550 · Callbacks are not run in an "eval" block, so calling "die()" will
551 throw a fatal exception. (Most event loops, however will catch the
552 exception, warn, and continue running.)
553
554 The code can be rewritten using "done()" instead of "then()" and an
555 event loop specific backend, and it will happily process millions of
556 lines without memory leaks or stack oveflows:
557
558 use Promises backend => ['EV'], 'deferred';
559
560 sub process_file {
561 my $fh = shift;
562 my $deferred = deferred;
563 my $processor = sub {
564 my $line = <$fh>;
565 unless (defined $line) {
566 # we're done
567 return $deferred->resolve;
568 }
569 #### USE done() TO END THE CHAIN ####
570 process_line($line)->done(
571
572 # on success, call $processor again
573 __SUB__,
574
575 # on failure:
576 sub {
577 return $deferred->reject("Failed")
578 }
579 )
580 }
581
582 # start the loop
583 $processor->();
584
585 return $deferred->promise
586 }
587
589 Stevan Little <stevan.little@iinteractive.com>
590
592 This software is copyright (c) 2017, 2014, 2012 by Infinity
593 Interactive, Inc..
594
595 This is free software; you can redistribute it and/or modify it under
596 the same terms as the Perl 5 programming language system itself.
597
598
599
600perl v5.28.0 2017-10-29Promises::Cookbook::GentleIntro(3)