1Promises::Cookbook::RecUusresrioCno(n3t)ributed Perl DocPurmoemnitsaetsi:o:nCookbook::Recursion(3)
2
3
4

NAME

6       Promises::Cookbook::Recursion - Examples of recursive asynchronous
7       operations
8

VERSION

10       version 1.04
11

SYNOPSIS

13           package MyClass;
14
15           use Promises backend => ['AE'], 'deferred';
16
17           sub new                 {...}
18           sub process             {...}
19           sub is_finished         {...}
20           sub fetch_next_from_db  {...} # returns a promise
21
22           sub fetch_all {
23               my $self     = shift;
24               my $deferred = deferred;
25
26               $self->_fetch_loop($deferred);
27               return $deferred->promise;
28           }
29
30           sub _fetch_loop {
31               my ($self,$deferred) = @_;
32               if ( $self->is_finished ) {
33                   $deferred->resolve;
34                   return
35               }
36               $self->fetch_next_from_db
37                    ->then( sub { $self->process(@_) })
38                    ->done(
39                       sub { $self->_fetch_loop($deferred) }
40                       sub { $deferred->reject(@_) }
41                    );
42           }
43
44           package main;
45
46           my $cv  = AnyEvent->condvar;
47           my $obj = MyClass->new(...);
48           $obj->fetch_all->then(
49               sub { $cv->send(@_)          },
50               sub { $cv->croak('ERROR',@_) }
51           );
52
53           $cv->recv;
54

DESCRIPTION

56       While "collect()" allows you to wait for multiple promises which are
57       executing in parallel, sometimes you need to execute each step in
58       order, by using promises recursively. For instance:
59
60       1.  Fetch next page of results
61
62       2.  Process page of results
63
64       3.  If there are no more results, return success
65
66       4.  Otherwise, goto step 1
67
68       However, recursion can result in very deep stacks and out of memory
69       conditions.  There are two important steps for dealing with recursion
70       effectively.
71
72       The first is to use one of the event-loop backends:
73
74           use Promises backend => ['AE'], 'deferred';
75
76       While the default Promises::Deferred implementation calls the "then()"
77       callbacks synchronously, the event-loop backends call the callbacks
78       asynchronously in the context of the event loop.
79
80       However, each "promise" passes its return value on to the next
81       "promise" etc, so you still end up using a lot of memory with
82       recursion. We can avoid this by breaking the chain.
83
84       In our example, all we care about is whether all the steps in our
85       process completed successfully or not.  Each execution of steps 1 to 4
86       is independent. Step 1 does not need to receive the return value from
87       step 4.
88
89       We can break the chain by using "done()" instead of "then()".  While
90       "then()" returns a new "promise" to continue the chain, "done()" will
91       execute either the success callback or the error callback and return an
92       empty list, breaking the chain and rolling back the stack.
93
94       To work through the code in the "SYNOPSIS":
95
96           sub fetch_all {
97               my $self     = shift;
98               my $deferred = deferred;
99
100               $self->_fetch_loop($deferred);
101               return $deferred->promise;
102           }
103
104       The $deferred variable (and the promise that we return to the caller)
105       will either be resolved once all results have been fetched and
106       processed by the "_fetch_loop()", or rejected if an error occurs at any
107       stage of execution.
108
109           sub _fetch_loop {
110               my ($self,$deferred) = @_;
111
112               if ( $self->is_finished ) {
113                   $deferred->resolve;
114                   return;
115               }
116
117       If "is_finished" returns a true value (eg there are no more results to
118       fetch), then we can resolve our promise, indicating success, and exit
119       the loop.
120
121               $self->fetch_next_from_db
122                    ->then( sub { $self->process(@_) })
123                    ->done(
124                       sub { $self->_fetch_loop($deferred) }
125                       sub { $deferred->reject(@_) }
126                    );
127           }
128
129       Otherwise we fetch the next page of results aynchronously from the DB
130       and process them. If either of these steps (fetching or processing)
131       fails, then we signal failure by rejecting our deferred promise and
132       exiting the loop.  If there is no failure, we recurse back into our
133       loop by calling "_fetch_loop()" again.
134
135       However,this recursion happens asynchronously. What this code actually
136       does is to schedule the call to "_fetch_loop()" in the next tick of the
137       event loop. And because we used "done()" instead of "then()", we don't
138       wait around for the return result but instead return immediately,
139       exiting the current execution, discarding the return results and
140       rolling back the stack.
141

AUTHOR

143       Stevan Little <stevan.little@iinteractive.com>
144
146       This software is copyright (c) 2020, 2019, 2017, 2014, 2012 by Infinity
147       Interactive, Inc.
148
149       This is free software; you can redistribute it and/or modify it under
150       the same terms as the Perl 5 programming language system itself.
151
152
153
154perl v5.34.0                      2021-07-22  Promises::Cookbook::Recursion(3)
Impressum