1package Mojo::IOLoop::Delay;
2use Mojo::Base 'Mojo::Promise';
3
4our $VERSION = '8.76';
5$VERSION = eval $VERSION;
6
7require Mojo::IOLoop;
8unless (Mojo::IOLoop->can('delay')) {
9  require Mojo::Util;
10
11  Mojo::Util::monkey_patch 'Mojo::IOLoop', delay => sub {
12    my $self = shift;
13    my $loop = ref $self ? $self : $self->singleton;
14    my $delay = Mojo::IOLoop::Delay->new->ioloop($loop);
15    return @_ ? $delay->steps(@_) : $delay;
16  };
17}
18
19sub begin {
20  my ($self, $offset, $len) = @_;
21  $self->{pending}++;
22  my $id = $self->{counter}++;
23  return sub { $self->_step($id, $offset // 1, $len, @_) };
24}
25
26sub pass { $_[0]->begin->(@_) }
27
28sub steps {
29  my ($self, @steps) = @_;
30  $self->{steps} = \@steps;
31  $self->ioloop->next_tick($self->begin);
32  return $self;
33}
34
35sub _step {
36  my ($self, $id, $offset, $len) = (shift, shift, shift, shift);
37
38  $self->{args}[$id] = [@_ ? defined $len ? splice @_, $offset, $len : splice @_, $offset : ()];
39  return $self if $self->{fail} || --$self->{pending} || $self->{lock};
40  local $self->{lock} = 1;
41  my @args = map {@$_} @{delete $self->{args}};
42
43  $self->{counter} = 0;
44  if (my $cb = shift @{$self->{steps}}) {
45    unless (eval { $self->$cb(@args); 1 }) {
46      my $err = $@;
47      @{$self}{qw(fail steps)} = (1, []);
48      return $self->reject($err);
49    }
50  }
51
52  ($self->{steps} = []) and return $self->resolve(@args) unless $self->{counter};
53  $self->ioloop->next_tick($self->begin)                 unless $self->{pending};
54  return $self;
55}
56
571;
58
59=encoding utf8
60
61=head1 NAME
62
63Mojo::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
105L<Mojo::IOLoop::Delay> adds flow-control helpers to L<Mojo::Promise>, which can help you avoid deep nested closures
106that 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
127The idea behind L<Mojo::IOLoop::Delay> is to turn the nested closures above into a flat series of closures. In the
128example below, the call to L</"begin"> creates a code reference that we can pass to L<Mojo::IOLoop/"timer"> as a
129callback, 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
149Another positive side effect of this pattern is that we do not need to call L<Mojo::IOLoop/"start"> and
150L<Mojo::IOLoop/"stop"> manually, because we know exactly when our chain of L</"steps"> has reached the end. So
151L<Mojo::Promise/"wait"> can stop the event loop automatically if it had to be started at all in the first place.
152
153=head1 DISCOURAGED! WARNING!
154
155This module has been extracted from L<Mojolicious> and was removed from it at the 9.0 release.
156It 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
158Though 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.
159It will receive no no-security-related changes going forward.
160
161=head1 MOJO::IOLOOP CLASS METHOD CONSTRUCTOR
162
163As of Mojolicious 9.0, the package L<Mojo::IOLoop> no longer provides a class constructor for delays.
164If you want to use L<< Mojo::IOLoop->delay >> you must first load this class explicitly which will add it back.
165You can also use C<-MMojo::IOLoop::Delay> at the command line to do so.
166
167=head1 ATTRIBUTES
168
169L<Mojo::IOLoop::Delay> inherits all attributes from L<Mojo::Promise>.
170
171=head1 METHODS
172
173L<Mojo::IOLoop::Delay> inherits all methods from L<Mojo::Promise> 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
181Indicate an active event by incrementing the event counter, the returned code reference can be used as a callback, and
182needs to be executed when the event has completed to decrement the event counter again. When all code references
183generated by this method have been executed and the event counter has reached zero, L</"steps"> 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
190Arguments passed to the returned code reference are spliced with the given offset and length, defaulting to an offset
191of C<1> with no default length. The arguments are then combined in the same order L</"begin"> was called, and passed
192together 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
215Shortcut for passing values between L</"steps">.
216
217  # Longer version
218  $delay->begin(0)->(@args);
219
220=head2 steps
221
222  $delay = $delay->steps(sub {...}, sub {...});
223
224Sequentialize multiple events, every time the event counter reaches zero a callback will run, the first one
225automatically runs during the next reactor tick unless it is delayed by incrementing the event counter. This chain will
226continue until there are no remaining callbacks, a callback does not increment the event counter or an exception gets
227thrown in a callback. Finishing the chain will also result in the promise being fulfilled, or if an exception got
228thrown it will be rejected.
229
230=head1 SEE ALSO
231
232L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
233
234=head1 AUTHORS
235
236The L<Mojolicious/AUTHORS>
237
238=head1 CONTACT
239
240While this module is no longer receiving non-security related maintenance, if you must contact someone about it please contact Joel Berger <jberger@cpan.org> or as a last resort contact the Mojolicious Core Team.
241
242=head1 COPYRIGHT AND LICENSE
243
244Copyright (C) 2008-2021, Sebastian Riedel and others.
245
246This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version
2472.0.
248
249=cut
250