昨天有人在群里问起Mojolicious/t/mojo/delay.t 中一段代码的执行原理。代码如下:
use Mojo::Base -strict;
BEGIN {
$ENV{MOJO_NO_IPV6} = 1;
$ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}
use Test::More;
use Mojo::IOLoop;
use Mojo::IOLoop::Delay;
my $delay = Mojo::IOLoop::Delay->new;
my $finished;
my $result = undef;
$delay->on(finish => sub { $finished++ });
$delay->steps(
sub {
my $delay = shift;
my $end = $delay->begin;
$delay->begin->(3, 2, 1);
Mojo::IOLoop->timer(0 => sub { $end->(1, 2, 3) });
},
sub {
my ($delay, @numbers) = @_;
my $end = $delay->begin;
Mojo::IOLoop->timer(0 => sub { $end->(undef, @numbers, 4) });
},
sub {
my ($delay, @numbers) = @_;
$result = \@numbers;
}
);
is_deeply [$delay->wait], [2, 3, 2, 1, 4], 'right return values';
is $finished, 1, 'finish event has been emitted once';
is_deeply $result, [2, 3, 2, 1, 4], 'right results';
done_testing();
首先介绍一下这个 Mojo::IOLoop::Delay
模块,这是异步编程中很火很实用的一个概念,一般叫 Promise
/ Deferred
。你可以按照顺序编程的思路组合那些异步函数,比如在这个例子里主要就体现了 steps
方法和 finish
事件。
steps
方法中可以传递任意多个异步函数。第一个函数立刻执行,然后等 $delay
信号量(由 begin
方法控制)释放(即重新等于0)后逐次执行后面的函数,直到碰到一个不调用 begin
控制信号量的函数,或者触发 error
或者 finish
事件。
begin
方法返回的回调函数 $end->()
用来减信号量。如果传递了参数给这个回调函数,那么第一个参数会被忽略,剩下的参数会 push
进下一个顺序或者事件触发函数的参数列表里,同时推送到 wait
方法。
所以上面这段测试的数据执行结果是这样的:
$delay->wait
开始整个 ioloop
, steps
方法首先执行 sub1 ,首先通过 $delay->begin()
给信号量加1;timer
事件,$end->(1, 2, 3)
将 (2, 3)
推入下一个函数 sub2 的 @_
里,同时把信号量减1;$delay->begin()->(3, 2, 1)
,将 (2, 1)
推入下一个函数 sub2 的 @_
里,注意这里信号量实际也加减过一次,只是这里的回调函数直接匿名调用了;($delay, (2, 3), (2, 1))
,也就是说这时候的 @numbers
是 (2, 3, 2, 1)
;timer
事件,然后 $end->(undef, @numbers, 4)
把 ((2, 3, 2, 1), 4)
推入下一个函数 sub3 的 @_
里,同时信号量减1;($delay, (2, 3, 2, 1, 4))
,也就是说这时候的 @numbers
是 (2, 3, 2, 1, 4)
;@numbers
的引用赋值给 $result
,因为 sub3 里没有对信号量的操作,而且也是最后一个了,steps
完成,触发 finish
事件;finish
事件回调函数把 $finish
变量加1;$delay->wait
这时候也收集完毕前面每个 $end->()
的参数列表,和每步 @numbers
是同步的,同时因为 finish
事件被触发,就此停止 ioloop
,程序完成,返回整个列表。如上。