第三天 - 高阶 Promises
— 焉知非鱼通过组合 Promises 来创建新的复杂 Promise #
Mojolicious 7.49 添加了自己的 Promises/A+ 规范实现。莫霍克在第14天写到了这些:你答应召唤! 在 2017 年的 Mojolicious Advent Calender 中,他向你展示了如何同时获取多个网页。这个 Advent 条目扩展了更多的 Promise 技巧。
Promise 是一种旨在消除嵌套回调(也称为“回调地狱”)的结构。正确编写的 Promises 链具有扁平结构,易于线性跟随。
高阶 Promise 是包含其他 Promise 并以其身份为基础的 Promise。 Mojo::Promise::Role::HigherOrder 发行版提供了三个角色,您可以将它们混合到 Mojo::Promise
中以创建包含 Promises 的更好看的 Promise。不过,在你看到它们之前,先看看 Mojo::Promise 已经提供的两个。
All #
只有当 all
Promise 都解决时,所有承诺才会解决。如果其中一个被拒绝,则拒绝所有承诺。这意味着如果一个被拒绝并且不需要知道任何其他人的状态,整个 Promise 知道该怎么做。
use Mojo::Promise;
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
my @urls = ( ... );
my @all_sites = map { $ua->get_p( $_ ) } @urls;
my $all_promise = Mojo::Promise
->all( @all_sites )
->then(
sub { say "They all worked!" },
sub { say "One of them didn't work!" }
);
然而,Promise 不需要以任何顺序完成他们的工作,所以不要以此为基础。
先到先得 #
当第一个 Promise 不再等待时,“race”就会解决,之后不需要其他 Promise 继续工作。
use Mojo::Promise;
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
my @urls = ( ... );
my @all_sites = map { $ua->get_p( $_ ) } @urls;
my $all_promise = Mojo::Promise
->race( @all_sites )
->then(
sub { say "One of them finished!" },
);
Any #
“any” 承诺在其第一个承诺结算时立即解决。这与 race
略有不同,因为至少有一个 Promise 必须解决。被拒绝的承诺并没有像 race
那样解决 any
问题。这应该像 bluebirdjs 中的 any
一样。
这是一个程序,它提取配置的 CPAN 镜像并测试它可以获取 index.html 文件。为了确保它找到该文件而不是某个强制门户网站,它会在正文中查找“Jarkko”:
use v5.28;
use utf8;
use strict;
use warnings;
use feature qw(signatures);
no warnings qw(experimental::signatures);
use File::Spec::Functions;
use Mojo::Promise;
use Mojo::Promise::Role::HigherOrder;
use Mojo::UserAgent;
use Mojo::URL;
use lib catfile( $ENV{HOME}, '.cpan' );
my @mirrors = eval {
no warnings qw(once);
my $file = Mojo::URL->new( 'index.html' );
require CPAN::MyConfig;
map { say "1: $_"; $file->clone->base(Mojo::URL->new($_))->to_abs }
$CPAN::Config->{urllist}->@*
};
die "Did not find CPAN::MyConfig\n" unless @mirrors;
my $ua = Mojo::UserAgent->new;
my @all_sites = map {
$ua->get_p( $_ )->then( sub ($tx) {
die unless $tx->result->body =~ /Jarkko/ });
} @mirrors;
my $any_promise = Mojo::Promise
->with_roles( '+Any' )
->any( @all_sites )
->then(
sub { say "At least one of them worked!" },
sub { say "None of them worked!" },
);
$any_promise->wait;
Some #
当一定数量的 Promise 解决时,一些 Promise 会解决。您可以指定成功所需的数量,并在该数字解析时解决一些 Promise。这应该像 bluebirdjs 中的 some
一样。
此示例修改以前的程序以查找多个有效的镜像。您可以指定需要解决的问题的数量:
use v5.28;
use utf8;
use strict;
use warnings;
use feature qw(signatures);
no warnings qw(experimental::signatures);
use File::Spec::Functions;
use Mojo::Promise;
use Mojo::Promise::Role::HigherOrder;
use Mojo::UserAgent;
use Mojo::URL;
use lib catfile( $ENV{HOME}, '.cpan' );
my @mirrors = eval {
no warnings qw(once);
my $file = Mojo::URL->new( 'index.html' );
require CPAN::MyConfig;
map { say "1: $_"; $file->clone->base(Mojo::URL->new($_))->to_abs }
$CPAN::Config->{urllist}->@*
};
die "Did not find CPAN::MyConfig\n" unless @mirrors;
my $ua = Mojo::UserAgent->new;
my $count = 2;
my @all_sites = map {
$ua->get_p( $_ )->then( sub ($tx) {
die unless $tx->result->body =~ /Jarkko/ });
} @mirrors;
my $some_promise = Mojo::Promise
->with_roles( '+Some' )
->some( \@all_sites, 2 )
->then(
sub { say "At least $count of them worked!" },
sub { say "None of them worked!" },
);
$some_promise->wait;
None #
当所有的Promise被拒绝时,“none” Promise 就会解决。这是一个微不足道的案例,可能在某个地方很有用,而且我创建它主要是因为 Raku 有一个 none junction(实际上并不是一回事)。有时可能更容易检查没有兑现的承诺,而不是其中一个被拒绝。
对于这个非常简单的示例,请考虑检查没有站点是令人讨厌的“404 File Not Found”的任务:
use v5.28;
use utf8;
use strict;
use warnings;
use feature qw(signatures);
no warnings qw(experimental::signatures);
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
use Mojo::Promise;
use Mojo::Promise::Role::HigherOrder;
my @urls = qw(
https://www.learning-perl.com/
https://www.perl.org/
https://perldoc.perl.org/not_there.pod
);
my @all_sites = map {
my $p = $ua->get_p( $_ );
$p->then( sub ( $tx ) {
$tx->res->code == 404 ? $tx->req->url : die $tx->req->url
} );
} @urls;
my $all_promise = Mojo::Promise
->with_roles( '+None' )
->none( @all_sites )
->then(
sub { say "None of them were 404!" },
sub { say "At least one was 404: @_!" },
);
$all_promise->wait;
结论 #
很容易用较小的 Promise 制作新的 Promise 来代表复杂的情况。您可以将 Mojolicious 为您创造的 Promise 与您自己的手工制作 Promise 相结合,几乎可以做任何您喜欢的事情。