Newer Older
193 lines | 4.645kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojo::IOLoop::Delay;
2
use Mojo::Base 'Mojo::EventEmitter';
3

            
4
use Mojo::IOLoop;
5

            
6
has ioloop => sub { Mojo::IOLoop->singleton };
7

            
8
sub begin {
9
  my ($self, $ignore) = @_;
10
  $self->{pending}++;
11
  my $id = $self->{counter}++;
12
  return sub { $ignore // 1 and shift; $self->_step($id, @_) };
13
}
14

            
15
sub steps {
16
  my $self = shift;
17
  $self->{steps} = [@_];
18
  $self->ioloop->timer(0 => $self->begin);
19
  return $self;
20
}
21

            
22
sub wait {
23
  my $self = shift;
24

            
25
  my @args;
26
  $self->once(error => \&_die);
27
  $self->once(finish => sub { shift->ioloop->stop; @args = @_ });
28
  $self->ioloop->start;
29

            
30
  return wantarray ? @args : $args[0];
31
}
32

            
33
sub _die { $_[0]->has_subscribers('error') ? $_[0]->ioloop->stop : die $_[1] }
34

            
35
sub _step {
36
  my ($self, $id) = (shift, shift);
37

            
38
  $self->{args}[$id] = [@_];
39
  return 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
    eval { $self->$cb(@args); 1 } or return $self->emit(error => $@)->{fail}++;
46
  }
47

            
48
  return $self->emit(finish => @args) unless $self->{counter};
49
  $self->ioloop->timer(0 => $self->begin) unless $self->{pending};
50
}
51

            
52
1;
53

            
54
=encoding utf8
55

            
56
=head1 NAME
57

            
58
Mojo::IOLoop::Delay - Manage callbacks and control the flow of events
59

            
60
=head1 SYNOPSIS
61

            
62
  use Mojo::IOLoop::Delay;
63

            
64
  # Synchronize multiple events
65
  my $delay = Mojo::IOLoop::Delay->new;
66
  $delay->on(finish => sub { say 'BOOM!' });
67
  for my $i (1 .. 10) {
68
    my $end = $delay->begin;
69
    Mojo::IOLoop->timer($i => sub {
70
      say 10 - $i;
71
      $end->();
72
    });
73
  }
74
  $delay->wait unless Mojo::IOLoop->is_running;
75

            
76
  # Sequentialize multiple events
77
  my $delay = Mojo::IOLoop::Delay->new;
78
  $delay->steps(
79

            
80
    # First step (simple timer)
81
    sub {
82
      my $delay = shift;
83
      Mojo::IOLoop->timer(2 => $delay->begin);
84
      say 'Second step in 2 seconds.';
85
    },
86

            
87
    # Second step (parallel timers)
88
    sub {
89
      my ($delay, @args) = @_;
90
      Mojo::IOLoop->timer(1 => $delay->begin);
91
      Mojo::IOLoop->timer(3 => $delay->begin);
92
      say 'Third step in 3 seconds.';
93
    },
94

            
95
    # Third step (the end)
96
    sub {
97
      my ($delay, @args) = @_;
98
      say 'And done after 5 seconds total.';
99
    }
100
  );
101
  $delay->wait unless Mojo::IOLoop->is_running;
102

            
103
=head1 DESCRIPTION
104

            
105
L<Mojo::IOLoop::Delay> manages callbacks and controls the flow of events for
106
L<Mojo::IOLoop>.
107

            
108
=head1 EVENTS
109

            
110
L<Mojo::IOLoop::Delay> inherits all events from L<Mojo::EventEmitter> and can
111
emit the following new ones.
112

            
113
=head2 error
114

            
115
  $delay->on(error => sub {
116
    my ($delay, $err) = @_;
117
    ...
118
  });
119

            
120
Emitted if an error occurs in one of the steps, breaking the chain, fatal if
121
unhandled.
122

            
123
=head2 finish
124

            
125
  $delay->on(finish => sub {
126
    my ($delay, @args) = @_;
127
    ...
128
  });
129

            
130
Emitted once the active event counter reaches zero and there are no more
131
steps.
132

            
133
=head1 ATTRIBUTES
134

            
135
L<Mojo::IOLoop::Delay> implements the following attributes.
136

            
137
=head2 ioloop
138

            
139
  my $ioloop = $delay->ioloop;
140
  $delay     = $delay->ioloop(Mojo::IOLoop->new);
141

            
142
Event loop object to control, defaults to the global L<Mojo::IOLoop>
143
singleton.
144

            
145
=head1 METHODS
146

            
147
L<Mojo::IOLoop::Delay> inherits all methods from L<Mojo::EventEmitter> and
148
implements the following new ones.
149

            
150
=head2 begin
151

            
152
  my $without_first_arg = $delay->begin;
153
  my $with_first_arg    = $delay->begin(0);
154

            
155
Increment active event counter, the returned callback can be used to decrement
156
the active event counter again. Arguments passed to the callback are queued in
157
the right order for the next step or L</"finish"> event and L</"wait"> method,
158
the first argument will be ignored by default.
159

            
160
  # Capture all arguments
161
  my $delay = Mojo::IOLoop->delay;
162
  Mojo::IOLoop->client({port => 3000} => $delay->begin(0));
163
  my ($loop, $err, $stream) = $delay->wait;
164

            
165
=head2 steps
166

            
167
  $delay = $delay->steps(sub {...}, sub {...});
168

            
169
Sequentialize multiple events, the first callback will run right away, and the
170
next one once the active event counter reaches zero. This chain will continue
171
until there are no more callbacks, a callback does not increment the active
172
event counter or an error occurs in a callback.
173

            
174
=head2 wait
175

            
176
  my $arg  = $delay->wait;
177
  my @args = $delay->wait;
178

            
179
Start L</"ioloop"> and stop it again once an L</"error"> or L</"finish"> event
180
gets emitted, only works when L</"ioloop"> is not running already.
181

            
182
  # Use the "finish" event to synchronize portably
183
  $delay->on(finish => sub {
184
    my ($delay, @args) = @_;
185
    ...
186
  });
187
  $delay->wait unless $delay->ioloop->is_running;
188

            
189
=head1 SEE ALSO
190

            
191
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
192

            
193
=cut