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