昨天有人在群里问起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,程序完成,返回整个列表。如上。