->then(\&cb1)->then(\&cb2)->…
one call ->steps(\&cb1, \&cb2, …)
used ->steps(\&cb1, \&cb2, …)
.->then()
it is set via ->catch()
. Consequence: there can be only one error handler for all steps of this task.->pass()
, but unlike ->resolve()
in most cases it is called implicitly - the result of the call to the anonymous function generator is transferred to the asynchronous operation ->begin
, and the function returned by it automatically does ->pass()
, passing a slice of its parameters (ie, the result of the operation of an asynchronous operation) to the next step. Corollary: it is not necessary to write for each asynchronous callback function, which will convert the result returned by it to ->resolve()
and ->reject()
.->reject()
.->on(finish=>\&cb)
, which can also be accessed from the error handler.->wait
, everything is simple and clear from the official documentation. Mojo::IOLoop->delay(@params) # : Mojo::IOLoop::Delay->new->steps(@params)
$delay->catch(\&cb) # (.. $delay, \&cb, # ) : $delay->on(error=>\&cb)
->begin
increases the counter of running (usually asynchronous) operations and returns a reference to a new anonymous function . This returned function must be called once upon completion of the operation - it will reduce the counter of running operations and allow you to transfer the results of the operation to the next step (which will be started when the counter reaches zero).->begin
: manually and automatically.->begin
remembered in a temporary variable and upon completion of the operation it is called manually: my $delay = Mojo::IOLoop->delay; for my $i (1 .. 10) { my $end = $delay->begin; Mojo::IOLoop->timer($i => sub { say 10 - $i; $end->(); }); }
->begin
used as the callback for the operation: my $delay = Mojo::IOLoop->delay; for my $i (1 .. 10) { Mojo::IOLoop->timer($i => $delay->begin); }
$delay
(in this case, it is the first and only) step, then it will be called after all 10 operations are completed: $delay->steps(sub{ say "all timers done" });
say 10 - $i
not executed. the timer does not pass any parameters to its callback, and we cannot find out the value of $i
in the callback unless we shut it down as in the first version. But even if the timer passed $i
parameter to the callback, it wouldn't help you much anyway, because a chance to fulfill all ten say 10 - $i
we would get only in the next step, and it will start only after all the timers are completed - i.e. the countdown effect will disappear when say
performed once a second.->begin
. But in all others it is much better to use the second option: this will eliminate the time variable, the “noodles” of callbacks, and will make it possible to use (more precisely, intercept) exceptions in callbacks (the exception in the usual callback is not a “step” - will fall not into $delay->catch
but into the event loop exception handler and, by default, will be ignored).->begin
you can pass the parameters, and at first glance (in the official documentation) they may not look very clear. The bottom line is that when the function returned ->begin
used not in the manual version (when you call it yourself and control with what parameters it will be called), but as a direct callback for the operation, it will be called with the parameters with which it will cause this operation. And you will receive all these parameters as a result of this operation in the parameters of the next step.$ua->get($url,\&cb)
sends two parameters to the callback: ($ua,$tx)
, and if you start pumping out 3 url in one step, the next step will receive 6 parameters (each step gets the first required parameter is $ delay, and why in this example I use ->begin(0)
I will explain soon): Mojo::IOLoop->delay( sub { my ($delay) = @_; $ua->get($url1, $delay->begin(0)); $ua->get($url2, $delay->begin(0)); $ua->get($url3, $delay->begin(0)); }, sub { my ($delay, $ua1,$tx1, $ua2,$tx2, $ua3,$tx3) = @_; }, );
$ua
obtained by the second step will be the same. Since this is a typical situation, ->begin
gives you the ability to control which of the parameters passed by the operation should be passed on to the next step. To do this, it takes two parameters: the index of the first parameter and their number in order to pass the slice to the next step. By default ->begin
works like ->begin(1)
- i.e. passes to the next step all parameters passed by the operation except the first: Mojo::IOLoop->delay( sub { my ($delay) = @_; $ua->get($url1, $delay->begin); $ua->get($url2, $delay->begin); $ua->get($url3, $delay->begin); }, sub { my ($delay, $tx1, $tx2, $tx3) = @_; }, );
->data
everything is trivial: a hash accessible to all steps is an alternative to transferring data from one step to another through parameters. Mojo::IOLoop->delay( sub { my ($delay) = @_; $delay->data->{key} = 'value'; ... }, sub { my ($delay) = @_; say $delay->data->{key}; }, );
sub do_task { my $key; Mojo::IOLoop->delay( sub { $key = 'value'; ... }, sub { say $key; }, ); }
sub do_task { my $ua = Mojo::UserAgent->new->max_redirects(5); Mojo::IOLoop->delay( sub { my ($delay) = @_; $ua->get($url1, $delay->begin); $ua->get($url2, $delay->begin); $ua->get($url3, $delay->begin); }, sub { my ($delay, $tx1, $tx2, $tx3) = @_; # $tx " " }, ); }
$ua
variable will be deleted as well. no more steps that refer to it. And as soon as $ua
is removed, all open connections related to it will be broken and their callback will be called with an error in the $tx
parameter.->data
to guarantee the lifetime of the closet no less than the execution time of the entire task: sub do_task { my $ua = Mojo::UserAgent->new->max_redirects(5); Mojo::IOLoop->delay->data(ua=>$ua)->steps( sub { my ($delay) = @_; $ua->get($url1, $delay->begin); $ua->get($url2, $delay->begin); $ua->get($url3, $delay->begin); }, sub { my ($delay, $tx1, $tx2, $tx3) = @_; # $tx }, ); }
->catch
, and there are no fatal errors, after which it still makes sense to finish the current task on a regular basis by completing the last step - the exception handler can transfer control to the “finish” handler via ->emit("finish",@results)
but can not the usual step.->remaining([])->pass(@result)
.@result
parameters, but and all that will return operations.->emit("finish")
only inside the exception handler, but you cannot do it in a normal step. At the same time, in the usual step this is done through ->remaining([])->pass(@result)
, but this will not work in the exception handler.if
or in a loop that can have 0 iterations. In this case, as a rule, it is necessary that this step (usually at the very beginning or end) triggers: $delay->pass;
$url_login
), then go to the page with the list of necessary entries ( $url_list
), for some entries there may be a link to the page with details, and on the page with details there may be links to several files attached to this entry that need to be downloaded. sub parse_site { my ($user, $pass) = @_; # : # @records = ( # { # key1 => "value1", # … # attaches => [ "content of file1", … ], # }, # … # ); my @records; # $ua, .. # $user/$pass, # $ua my $ua = Mojo::UserAgent->new->max_redirects(5); # , $ua Mojo::IOLoop->delay->data(ua=>$ua)->steps( sub { $ua->post($url_login, form=>{user=>$user,pass=>$pass}, shift->begin); }, sub { my ($delay, $tx) = @_; die $tx->error->{message} if $tx->error; # if (!$tx->res->dom->at('#logout')) { die 'failed to login: bad user/pass'; } # , $ua->get($url_list, $delay->begin); }, sub { my ($delay, $tx) = @_; die $tx->error->{message} if $tx->error; # # - $delay->pass; # for ($tx->res->dom('.record')->each) { # my $record = { key1 => $_->at('.key1')->text, # … }; # push @records, $record; # - if (my $a = $_->at('.details a')) { # # - # , .. # # # ( # $record # # ) - # , # # # Mojo::IOLoop->delay( sub { $ua->get($a->{href}, shift->begin); }, sub { my ($delay, $tx) = @_; die $tx->error->{message} if $tx->error; # - . $delay->pass; # 0 $tx->res->dom('.file a')->each(sub{ $ua->get($_->{href}, $delay->begin); }); }, sub { my ($delay, @tx) = @_; die $_->error->{message} for grep {$_->error} @tx; # for my $tx (@tx) { push @{ $record->{attaches} }, $tx->body; } # finish # , @tx, : $delay->pass; }, )->catch( sub { my ($delay, $err) = @_; warn $err; # $delay->emit(finish => 'failed to get details'); } )->on(finish => $delay->begin); } ### if .details } ### for .record }, )->catch( sub { my ($delay, $err) = @_; warn $err; # , $delay->emit(finish => 'failed to get records'); } )->on(finish => sub { my ($delay, @err) = @_; if (!@err) { process_records(@records); } } ); }
@records
), an empty list is transmitted to the next step (through $delay->pass;
), and an error text is sent to the next step. Thus, if the last step in the finish handler receives some parameters, it means there was an error (s) somewhere in the process of pumping out or parsing. The error itself has already been intercepted and processed (through warn
) in the handlers ->catch
- in fact, this is exactly what provided the transfer of the error with the parameter to the finish handler.Source: https://habr.com/ru/post/228141/
All Articles