File Coverage

blib/lib/Mojo/IOLoop/Delay.pm
Criterion Covered Total %
statement 37 37 100.0
branch 16 18 88.8
condition 9 10 90.0
subroutine 7 7 100.0
pod 3 3 100.0
total 72 75 96.0


line stmt bran cond sub pod time code
1             package Mojo::IOLoop::Delay;
2 1     1   371695 use Mojo::Base 'Mojo::Promise';
  1         2  
  1         5  
3              
4             our $VERSION = '8.76';
5             $VERSION = eval $VERSION;
6              
7             require Mojo::IOLoop;
8             unless (Mojo::IOLoop->can('delay')) {
9             require Mojo::Util;
10              
11             Mojo::Util::monkey_patch 'Mojo::IOLoop', delay => sub {
12 2     2   2323 my $self = shift;
13 2 50       7 my $loop = ref $self ? $self : $self->singleton;
14 2         10 my $delay = Mojo::IOLoop::Delay->new->ioloop($loop);
15 2 50       55 return @_ ? $delay->steps(@_) : $delay;
16             };
17             }
18              
19             sub begin {
20 53     53 1 10182 my ($self, $offset, $len) = @_;
21 53         72 $self->{pending}++;
22 53         72 my $id = $self->{counter}++;
23 53   100 53   202 return sub { $self->_step($id, $offset // 1, $len, @_) };
  53         3232  
24             }
25              
26 5     5 1 17 sub pass { $_[0]->begin->(@_) }
27              
28             sub steps {
29 13     13 1 21579 my ($self, @steps) = @_;
30 13         25 $self->{steps} = \@steps;
31 13         29 $self->ioloop->next_tick($self->begin);
32 13         273 return $self;
33             }
34              
35             sub _step {
36 53     53   84 my ($self, $id, $offset, $len) = (shift, shift, shift, shift);
37              
38 53 100       149 $self->{args}[$id] = [@_ ? defined $len ? splice @_, $offset, $len : splice @_, $offset : ()];
    100          
39 53 100 100     230 return $self if $self->{fail} || --$self->{pending} || $self->{lock};
      100        
40 24         49 local $self->{lock} = 1;
41 24         28 my @args = map {@$_} @{delete $self->{args}};
  52         74  
  24         44  
42              
43 24         43 $self->{counter} = 0;
44 24 100       26 if (my $cb = shift @{$self->{steps}}) {
  24         51  
45 23 100       27 unless (eval { $self->$cb(@args); 1 }) {
  23         54  
  19         340  
46 4         25 my $err = $@;
47 4         6 @{$self}{qw(fail steps)} = (1, []);
  4         9  
48 4         15 return $self->reject($err);
49             }
50             }
51              
52 20 100 50     62 ($self->{steps} = []) and return $self->resolve(@args) unless $self->{counter};
53 10 100       21 $self->ioloop->next_tick($self->begin) unless $self->{pending};
54 10         135 return $self;
55             }
56              
57             1;
58              
59             =encoding utf8
60              
61             =head1 NAME
62              
63             Mojo::IOLoop::Delay - (DISCOURAGED) Promises/A+ and flow-control helpers
64              
65             =head1 SYNOPSIS
66              
67             use Mojo::IOLoop::Delay;
68              
69             # Synchronize multiple non-blocking operations
70             my $delay = Mojo::IOLoop::Delay->new;
71             $delay->steps(sub { say 'BOOM!' });
72             for my $i (1 .. 10) {
73             my $end = $delay->begin;
74             Mojo::IOLoop->timer($i => sub {
75             say 10 - $i;
76             $end->();
77             });
78             }
79             $delay->wait;
80              
81             # Sequentialize multiple non-blocking operations
82             Mojo::IOLoop::Delay->new->steps(
83              
84             # First step (simple timer)
85             sub ($delay) {
86             Mojo::IOLoop->timer(2 => $delay->begin);
87             say 'Second step in 2 seconds.';
88             },
89              
90             # Second step (concurrent timers)
91             sub ($delay, @args) {
92             Mojo::IOLoop->timer(1 => $delay->begin);
93             Mojo::IOLoop->timer(3 => $delay->begin);
94             say 'Third step in 3 seconds.';
95             },
96              
97             # Third step (the end)
98             sub ($delay, @args) {
99             say 'And done after 5 seconds total.';
100             }
101             )->wait;
102              
103             =head1 DESCRIPTION
104              
105             L adds flow-control helpers to L, which can help you avoid deep nested closures
106             that often result from continuation-passing style.
107              
108             use Mojo::IOLoop;
109              
110             # These deep nested closures are often referred to as "Callback Hell"
111             Mojo::IOLoop->timer(3 => sub ($loop) {
112              
113             say '3 seconds';
114             Mojo::IOLoop->timer(3 => sub ($loop) {
115              
116             say '6 seconds';
117             Mojo::IOLoop->timer(3 => sub ($loop) {
118              
119             say '9 seconds';
120             Mojo::IOLoop->stop;
121             });
122             });
123             });
124              
125             Mojo::IOLoop->start;
126              
127             The idea behind L is to turn the nested closures above into a flat series of closures. In the
128             example below, the call to L creates a code reference that we can pass to L as a
129             callback, and that leads to the next closure in the series when executed.
130              
131             use Mojo::IOLoop;
132             use Mojo::IOLoop::Delay; # adds Mojo::IOLoop->delay
133              
134             # Instead of nested closures we now have a simple chain of steps
135             my $delay = Mojo::IOLoop->delay(
136             sub ($delay) { Mojo::IOLoop->timer(3 => $delay->begin) },
137             sub ($delay) {
138             say '3 seconds';
139             Mojo::IOLoop->timer(3 => $delay->begin);
140             },
141             sub ($delay) {
142             say '6 seconds';
143             Mojo::IOLoop->timer(3 => $delay->begin);
144             },
145             sub ($delay) { say '9 seconds' }
146             );
147             $delay->wait;
148              
149             Another positive side effect of this pattern is that we do not need to call L and
150             L manually, because we know exactly when our chain of L has reached the end. So
151             L can stop the event loop automatically if it had to be started at all in the first place.
152              
153             =head1 DISCOURAGED! WARNING!
154              
155             This module has been extracted from L and was removed from it at the 9.0 release.
156             It is kept here for backwards compatibility purposes but there is no intention to maintain it further and it should be migrated away from as your earliest convenience.
157              
158             Though there is no intention of removing it from CPAN in the future it should be treated as deprecated and the metadata will mark it as such.
159             It will receive no no-security-related changes going forward.
160              
161             =head1 MOJO::IOLOOP CLASS METHOD CONSTRUCTOR
162              
163             As of Mojolicious 9.0, the package L no longer provides a class constructor for delays.
164             If you want to use L<< Mojo::IOLoop->delay >> you must first load this class explicitly which will add it back.
165             You can also use C<-MMojo::IOLoop::Delay> at the command line to do so.
166              
167             =head1 ATTRIBUTES
168              
169             L inherits all attributes from L.
170              
171             =head1 METHODS
172              
173             L inherits all methods from L and implements the following new ones.
174              
175             =head2 begin
176              
177             my $cb = $delay->begin;
178             my $cb = $delay->begin($offset);
179             my $cb = $delay->begin($offset, $len);
180              
181             Indicate an active event by incrementing the event counter, the returned code reference can be used as a callback, and
182             needs to be executed when the event has completed to decrement the event counter again. When all code references
183             generated by this method have been executed and the event counter has reached zero, L will continue.
184              
185             # Capture all arguments except for the first one (invocant)
186             my $delay = Mojo::IOLoop->delay(sub ($delay, $err, $stream) { ... });
187             Mojo::IOLoop->client({port => 3000} => $delay->begin);
188             $delay->wait;
189              
190             Arguments passed to the returned code reference are spliced with the given offset and length, defaulting to an offset
191             of C<1> with no default length. The arguments are then combined in the same order L was called, and passed
192             together to the next step.
193              
194             # Capture all arguments
195             my $delay = Mojo::IOLoop->delay(sub ($delay, $loop, $err, $stream) { ... });
196             Mojo::IOLoop->client({port => 3000} => $delay->begin(0));
197             $delay->wait;
198              
199             # Capture only the second argument
200             my $delay = Mojo::IOLoop->delay(sub ($delay, $err) { ... });
201             Mojo::IOLoop->client({port => 3000} => $delay->begin(1, 1));
202             $delay->wait;
203              
204             # Capture and combine arguments
205             my $delay = Mojo::IOLoop->delay(sub ($delay, $three_err, $three_stream, $four_err, $four_stream) { ... });
206             Mojo::IOLoop->client({port => 3000} => $delay->begin);
207             Mojo::IOLoop->client({port => 4000} => $delay->begin);
208             $delay->wait;
209              
210             =head2 pass
211              
212             $delay = $delay->pass;
213             $delay = $delay->pass(@args);
214              
215             Shortcut for passing values between L.
216              
217             # Longer version
218             $delay->begin(0)->(@args);
219              
220             =head2 steps
221              
222             $delay = $delay->steps(sub {...}, sub {...});
223              
224             Sequentialize multiple events, every time the event counter reaches zero a callback will run, the first one
225             automatically runs during the next reactor tick unless it is delayed by incrementing the event counter. This chain will
226             continue until there are no remaining callbacks, a callback does not increment the event counter or an exception gets
227             thrown in a callback. Finishing the chain will also result in the promise being fulfilled, or if an exception got
228             thrown it will be rejected.
229              
230             =head1 SEE ALSO
231              
232             L, L, L.
233              
234             =head1 AUTHORS
235              
236             The L
237              
238             =head1 CONTACT
239              
240             While this module is no longer receiving non-security related maintenance, if you must contact someone about it please contact Joel Berger or as a last resort contact the Mojolicious Core Team.
241              
242             =head1 COPYRIGHT AND LICENSE
243              
244             Copyright (C) 2008-2021, Sebastian Riedel and others.
245              
246             This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version
247             2.0.
248              
249             =cut