commit perl-Mojolicious for openSUSE:Factory
Hello community, here is the log from the commit of package perl-Mojolicious for openSUSE:Factory checked in at 2019-12-30 12:34:48 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/perl-Mojolicious (Old) and /work/SRC/openSUSE:Factory/.perl-Mojolicious.new.6675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "perl-Mojolicious" Mon Dec 30 12:34:48 2019 rev:119 rq:759925 version:8.29 Changes: -------- --- /work/SRC/openSUSE:Factory/perl-Mojolicious/perl-Mojolicious.changes 2019-12-06 12:08:53.260126543 +0100 +++ /work/SRC/openSUSE:Factory/.perl-Mojolicious.new.6675/perl-Mojolicious.changes 2019-12-30 12:34:55.795807706 +0100 @@ -1,0 +2,14 @@ +Sun Dec 29 03:09:13 UTC 2019 - <timueller+perl@suse.de> + +- updated to 8.29 + see /usr/share/doc/packages/perl-Mojolicious/Changes + + 8.29 2019-12-28 + - Improved async/await support to work in many more cases, such as WebSocket + handlers. + + 8.28 2019-12-26 + - Added EXPERIMENTAL support for async/await (with -async Mojo::Base flag). + - Added EXPERIMENTAL all_settled and any methods to Mojo::Promise. + +------------------------------------------------------------------- Old: ---- Mojolicious-8.27.tar.gz New: ---- Mojolicious-8.29.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ perl-Mojolicious.spec ++++++ --- /var/tmp/diff_new_pack.Y3U8cD/_old 2019-12-30 12:34:56.243807946 +0100 +++ /var/tmp/diff_new_pack.Y3U8cD/_new 2019-12-30 12:34:56.243807946 +0100 @@ -17,7 +17,7 @@ Name: perl-Mojolicious -Version: 8.27 +Version: 8.29 Release: 0 %define cpan_name Mojolicious Summary: Real-time web framework ++++++ Mojolicious-8.27.tar.gz -> Mojolicious-8.29.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/Changes new/Mojolicious-8.29/Changes --- old/Mojolicious-8.27/Changes 2019-12-04 18:30:14.000000000 +0100 +++ new/Mojolicious-8.29/Changes 2019-12-28 00:31:06.000000000 +0100 @@ -1,4 +1,12 @@ +8.29 2019-12-28 + - Improved async/await support to work in many more cases, such as WebSocket + handlers. + +8.28 2019-12-26 + - Added EXPERIMENTAL support for async/await (with -async Mojo::Base flag). + - Added EXPERIMENTAL all_settled and any methods to Mojo::Promise. + 8.27 2019-12-04 - Added EXPERIMENTAL before_command hook. - Added EXPERIMENTAL scope_guard function to Mojo::Util. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/MANIFEST new/Mojolicious-8.29/MANIFEST --- old/Mojolicious-8.27/MANIFEST 2019-12-04 20:47:12.000000000 +0100 +++ new/Mojolicious-8.29/MANIFEST 2019-12-28 17:57:13.000000000 +0100 @@ -216,6 +216,7 @@ t/mojo/path.t t/mojo/prefork.t t/mojo/promise.t +t/mojo/promise_async_await.t t/mojo/proxy.t t/mojo/psgi.t t/mojo/reactor_detect.t diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/META.json new/Mojolicious-8.29/META.json --- old/Mojolicious-8.27/META.json 2019-12-04 20:47:12.000000000 +0100 +++ new/Mojolicious-8.29/META.json 2019-12-28 17:57:13.000000000 +0100 @@ -4,7 +4,7 @@ "Sebastian Riedel <sri@cpan.org>" ], "dynamic_config" : 0, - "generated_by" : "ExtUtils::MakeMaker version 7.38, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.42, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], @@ -62,6 +62,6 @@ }, "x_IRC" : "irc://irc.freenode.net/#mojo" }, - "version" : "8.27", + "version" : "8.29", "x_serialization_backend" : "JSON::PP version 4.04" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/META.yml new/Mojolicious-8.29/META.yml --- old/Mojolicious-8.27/META.yml 2019-12-04 20:47:12.000000000 +0100 +++ new/Mojolicious-8.29/META.yml 2019-12-28 17:57:12.000000000 +0100 @@ -7,7 +7,7 @@ configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 -generated_by: 'ExtUtils::MakeMaker version 7.38, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.42, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -34,5 +34,5 @@ homepage: https://mojolicious.org license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/mojolicious/mojo.git -version: '8.27' +version: '8.29' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/README.md new/Mojolicious-8.29/README.md --- old/Mojolicious-8.27/README.md 2019-11-21 17:49:22.000000000 +0100 +++ new/Mojolicious-8.29/README.md 2019-12-26 16:13:28.000000000 +0100 @@ -34,8 +34,8 @@ applications, independently of the web framework. * Full stack HTTP and WebSocket client/server implementation with IPv6, TLS, SNI, IDNA, HTTP/SOCKS5 proxy, UNIX domain socket, Comet (long polling), - Promises/A+, keep-alive, connection pooling, timeout, cookie, multipart, - and gzip compression support. + Promises/A+, async/await, keep-alive, connection pooling, timeout, cookie, + multipart, and gzip compression support. * Built-in non-blocking I/O web server, supporting multiple event loops as well as optional pre-forking and hot deployment, perfect for building highly scalable web services. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/lib/Mojo/Base.pm new/Mojolicious-8.29/lib/Mojo/Base.pm --- old/Mojolicious-8.27/lib/Mojo/Base.pm 2019-11-25 22:39:57.000000000 +0100 +++ new/Mojolicious-8.29/lib/Mojo/Base.pm 2019-12-26 16:50:14.000000000 +0100 @@ -20,6 +20,13 @@ use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 }); +# async/await support requires Future::AsyncAwait::Frozen 0.36+ +use constant ASYNC => $ENV{MOJO_NO_ASYNC} ? 0 : !!(eval { + require Future::AsyncAwait::Frozen; + Future::AsyncAwait::Frozen->VERSION('0.000001'); + 1; +}); + # Protect subclasses using AUTOLOAD sub DESTROY { } @@ -120,6 +127,14 @@ eval "package $caller; use Role::Tiny; 1" or die $@; } + # async/await + elsif ($flag eq '-async') { + Carp::croak 'Future::AsyncAwait::Frozen 0.36+ is required for async/await' + unless ASYNC; + Future::AsyncAwait::Frozen->import_into($caller, + future_class => 'Mojo::Promise'); + } + # Signatures (Perl 5.20+) elsif ($flag eq '-signatures') { Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020; @@ -257,6 +272,15 @@ use Mojo::Base 'SomeBaseClass', -signatures; use Mojo::Base -role, -signatures; +If you have L<Future::AsyncAwait::Frozen> 0.36+ installed you can also use the +C<-async> flag to activate the C<async> and C<await> keywords to deal much more +efficiently with promises. Note that this feature is B<EXPERIMENTAL> and might +change without warning! + + # Also enable async/await + use Mojo::Base -strict, -async; + use Mojo::Base -base, -signatures, -async; + This will also disable experimental warnings on versions of Perl where this feature was still experimental. @@ -264,7 +288,7 @@ Fluent interfaces are a way to design object-oriented APIs around method chaining to create domain-specific languages, with the goal of making the -readablity of the source code close to written prose. +readability of the source code close to written prose. package Duck; use Mojo::Base -base; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/lib/Mojo/Promise.pm new/Mojolicious-8.29/lib/Mojo/Promise.pm --- old/Mojolicious-8.27/lib/Mojo/Promise.pm 2019-11-25 22:39:45.000000000 +0100 +++ new/Mojolicious-8.29/lib/Mojo/Promise.pm 2019-12-28 17:51:50.000000000 +0100 @@ -7,25 +7,35 @@ has ioloop => sub { Mojo::IOLoop->singleton }, weak => 1; -sub all { - my ($class, @promises) = @_; +sub AWAIT_CLONE { _await('clone', @_) } - my $all = $promises[0]->clone; - my $results = []; - my $remaining = scalar @promises; - for my $i (0 .. $#promises) { - $promises[$i]->then( - sub { - $results->[$i] = [@_]; - $all->resolve(@$results) if --$remaining <= 0; - }, - sub { $all->reject(@_) } - ); - } +sub AWAIT_DONE { shift->resolve(@_) } +sub AWAIT_FAIL { shift->reject(@_) } - return $all; +sub AWAIT_GET { + my $self = shift; + my @results = @{$self->{result} // []}; + die $results[0] unless $self->{status} eq 'resolve'; + return wantarray ? @results : $results[0]; } +sub AWAIT_IS_CANCELLED {undef} + +sub AWAIT_IS_READY { + my $self = shift; + return !!$self->{result} && !@{$self->{resolve}} && !@{$self->{reject}}; +} + +sub AWAIT_NEW_DONE { _await('resolve', @_) } +sub AWAIT_NEW_FAIL { _await('reject', @_) } + +sub AWAIT_ON_CANCEL { } +sub AWAIT_ON_READY { shift->finally(@_) } + +sub all { _all(2, @_) } +sub all_settled { _all(0, @_) } +sub any { _all(3, @_) } + sub catch { shift->then(undef, shift) } sub clone { $_[0]->new->ioloop($_[0]->ioloop) } @@ -44,7 +54,7 @@ sub map { my ($class, $options) = (shift, ref $_[0] eq 'HASH' ? shift : {}); - my ($cb, @items) = @_; + my ($cb, @items) = @_; my @start = map { $_->$cb } splice @items, 0, $options->{concurrency} // @items; @@ -80,12 +90,7 @@ return $self; } -sub race { - my ($class, @promises) = @_; - my $new = $promises[0]->clone; - $_->then(sub { $new->resolve(@_) }, sub { $new->reject(@_) }) for @promises; - return $new; -} +sub race { _all(1, @_) } sub reject { shift->_settle('reject', @_) } sub resolve { shift->_settle('resolve', @_) } @@ -113,12 +118,72 @@ $loop->start until $done; } +sub _all { + my ($type, $class, @promises) = @_; + + my $all = $promises[0]->clone; + my $results = []; + my $remaining = scalar @promises; + for my $i (0 .. $#promises) { + + # "race" + if ($type == 1) { + $promises[$i]->then(sub { $all->resolve(@_) }, sub { $all->reject(@_) }); + } + + # "all" + elsif ($type == 2) { + $promises[$i]->then( + sub { + $results->[$i] = [@_]; + $all->resolve(@$results) if --$remaining <= 0; + }, + sub { $all->reject(@_) } + ); + } + + # "any" + elsif ($type == 3) { + $promises[$i]->then( + sub { $all->resolve(@_) }, + sub { + $results->[$i] = [@_]; + $all->reject(@$results) if --$remaining <= 0; + } + ); + } + + # "all_settled" + else { + $promises[$i]->then( + sub { + $results->[$i] = {status => 'fulfilled', value => [@_]}; + $all->resolve(@$results) if --$remaining <= 0; + }, + sub { + $results->[$i] = {status => 'rejected', reason => [@_]}; + $all->resolve(@$results) if --$remaining <= 0; + } + ); + } + } + + return $all; +} + +sub _await { + my ($method, $class) = (shift, shift); + my $promise = $class->$method(@_); + $promise->{cycle} = $promise; + return $promise; +} + sub _defer { my $self = shift; return unless my $result = $self->{result}; my $cbs = $self->{status} eq 'resolve' ? $self->{resolve} : $self->{reject}; - @{$self}{qw(resolve reject)} = ([], []); + @{$self}{qw(cycle resolve reject)} = (undef, [], []); $self->ioloop->next_tick(sub { $_->(@$result) for @$cbs }); } @@ -178,7 +243,7 @@ # Wrap continuation-passing style APIs with promises my $ua = Mojo::UserAgent->new; - sub get { + sub get_p { my $promise = Mojo::Promise->new; $ua->get(@_ => sub { my ($ua, $tx) = @_; @@ -190,7 +255,7 @@ } # Perform non-blocking operations sequentially - get('https://mojolicious.org')->then(sub { + get_p('https://mojolicious.org')->then(sub { my $mojo = shift; say $mojo->res->code; return get('https://metacpan.org'); @@ -203,8 +268,8 @@ })->wait; # Synchronize non-blocking operations (all) - my $mojo = get('https://mojolicious.org'); - my $cpan = get('https://metacpan.org'); + my $mojo = get_p('https://mojolicious.org'); + my $cpan = get_p('https://metacpan.org'); Mojo::Promise->all($mojo, $cpan)->then(sub { my ($mojo, $cpan) = @_; say $mojo->[0]->res->code; @@ -215,8 +280,8 @@ })->wait; # Synchronize non-blocking operations (race) - my $mojo = get('https://mojolicious.org'); - my $cpan = get('https://metacpan.org'); + my $mojo = get_p('https://mojolicious.org'); + my $cpan = get_p('https://metacpan.org'); Mojo::Promise->race($mojo, $cpan)->then(sub { my $tx = shift; say $tx->req->url, ' won!'; @@ -285,8 +350,26 @@ Returns a new L<Mojo::Promise> object that either fulfills when all of the passed L<Mojo::Promise> objects have fulfilled or rejects as soon as one of them rejects. If the returned promise fulfills, it is fulfilled with the values from -the fulfilled promises in the same order as the passed promises. This method can -be useful for aggregating results of multiple promises. +the fulfilled promises in the same order as the passed promises. + +=head2 all_settled + + my $new = Mojo::Promise->all_settled(@promises); + +Returns a new L<Mojo::Promise> object that fulfills when all of the passed +L<Mojo::Promise> objects have fulfilled or rejected, with hash references that +describe the outcome of each promise. Note that this method is B<EXPERIMENTAL> +and might change without warning! + +=head2 any + + my $new = Mojo::Promise->any(@promises); + +Returns a new L<Mojo::Promise> object that fulfills as soon as one of +the passed L<Mojo::Promise> objects fulfills, with the value from that promise. +If no promises fulfill, it is rejected with the reasons from the rejected +promises in the same order as the passed promises. Note that this method is +B<EXPERIMENTAL> and might change without warning! =head2 catch diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/lib/Mojolicious/Command/version.pm new/Mojolicious-8.29/lib/Mojolicious/Command/version.pm --- old/Mojolicious-8.27/lib/Mojolicious/Command/version.pm 2019-11-25 22:40:02.000000000 +0100 +++ new/Mojolicious-8.29/lib/Mojolicious/Command/version.pm 2019-12-26 16:13:28.000000000 +0100 @@ -18,7 +18,8 @@ = Mojo::IOLoop::Client->can_socks ? $IO::Socket::Socks::VERSION : 'n/a'; my $tls = Mojo::IOLoop::TLS->can_tls ? $IO::Socket::SSL::VERSION : 'n/a'; my $nnr = Mojo::IOLoop::Client->can_nnr ? $Net::DNS::Native::VERSION : 'n/a'; - my $roles = Mojo::Base->ROLES ? $Role::Tiny::VERSION : 'n/a'; + my $roles = Mojo::Base->ROLES ? $Role::Tiny::VERSION : 'n/a'; + my $async = Mojo::Base->ASYNC ? $Future::AsyncAwait::Frozen::VERSION : 'n/a'; print <<EOF; CORE @@ -26,12 +27,13 @@ Mojolicious ($Mojolicious::VERSION, $Mojolicious::CODENAME) OPTIONAL - Cpanel::JSON::XS 4.09+ ($json) - EV 4.0+ ($ev) - IO::Socket::Socks 0.64+ ($socks) - IO::Socket::SSL 2.009+ ($tls) - Net::DNS::Native 0.15+ ($nnr) - Role::Tiny 2.000001+ ($roles) + Cpanel::JSON::XS 4.09+ ($json) + EV 4.0+ ($ev) + IO::Socket::Socks 0.64+ ($socks) + IO::Socket::SSL 2.009+ ($tls) + Net::DNS::Native 0.15+ ($nnr) + Role::Tiny 2.000001+ ($roles) + Future::AsyncAwait::Frozen 0.36+ ($async) EOF diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/lib/Mojolicious/Guides/Cookbook.pod new/Mojolicious-8.29/lib/Mojolicious/Guides/Cookbook.pod --- old/Mojolicious-8.27/lib/Mojolicious/Guides/Cookbook.pod 2019-11-21 17:49:24.000000000 +0100 +++ new/Mojolicious-8.29/lib/Mojolicious/Guides/Cookbook.pod 2019-12-26 17:24:12.000000000 +0100 @@ -649,6 +649,114 @@ L<Mojo::Promise/"resolve"> to transition them to C<fulfilled>, or L<Mojo::Promise/"reject"> to transition them to C<rejected>. +=head2 async/await + +And if you have L<Future::AsyncAwait::Frozen> installed you can make using +promises even easier. The C<async> and C<await> keywords are enabled with the +C<-async> flag of L<Mojo::Base>, and make the use of closures with promises +completely optional. + + use Mojo::Base -strict, -async; + +The C<async> keyword is placed before the C<sub> keyword, and means that this +function always returns a promise. Returned values that are not L<Mojo::Promise> +objects will be wrapped in a resolved promise automatically. And if an exception +gets thrown in the function it will result in a rejected promise. + + use Mojo::Base -strict, -async; + + async sub hello_p { + return 'Hello Mojo!'; + } + + hello_p()->then(sub { say @_ })->wait; + +The C<await> keyword on the other hand makes Perl wait for the promise to be +settled. It then returns the fulfillment values or throws an exception with the +rejection reason. While waiting, the event loop is free to perform other tasks +however, so no resources are wasted. + + use Mojo::Base -strict, -signatures, -async; + use Mojo::UserAgent; + use Mojo::URL; + + my $ua = Mojo::UserAgent->new; + + # Search MetaCPAN non-blocking for multiple terms sequentially + async sub search_cpan_p ($terms) { + my $cpan = Mojo::URL->new('http://fastapi.metacpan.org/v1/module/_search'); + my @urls = map { $cpan->clone->query(q => $_) } @$terms; + + for my $url (@urls) { + my $tx = await $ua->get_p($url); + say $tx->result->json('/hits/hits/0/_source/release'); + } + } + + search_cpan_p(['mojo', 'minion'])->wait; + +The loop above performs all requests sequentially, awaiting a result before +sending the next request. But you can also perform those requests concurrently +instead, by using methods like L<Mojo::Promise/"all"> to combine multiple +promises before awaiting the results. + + use Mojo::Base -strict, -signatures, -async; + use Mojo::Promise; + use Mojo::UserAgent; + use Mojo::URL; + + my $ua = Mojo::UserAgent->new; + + # Search MetaCPAN non-blocking for multiple terms concurrently + async sub search_cpan_p ($terms) { + my $cpan = Mojo::URL->new('http://fastapi.metacpan.org/v1/module/_search'); + my @urls = map { $cpan->clone->query(q => $_) } @$terms; + + my @promises = map { $ua->get_p($_) } @urls; + my @results = await Mojo::Promise->all(@promises); + for my $result (@results) { + say $result->[0]->result->json('/hits/hits/0/_source/release'); + } + } + + search_cpan_p(['mojo', 'minion'])->wait; + +All of this also means that you can use normal Perl exception handling again. +Even many 3rd party exception handling modules from CPAN work just fine. + + use Mojo::Base -strict, -async; + use Mojo::Promise; + + # Catch a non-blocking exception + async sub hello_p { + eval { await Mojo::Promise->reject('This is an exception') }; + if (my $err = $@) { warn "Error: $err" } + } + + hello_p()->wait; + +And it works just the same in L<Mojolicious> and L<Mojolicious::Lite> +applications. Just declare your actions with the C<async> keyword and use +C<await> to wait for promises to be C<fulfilled> or C<rejected>. + + use Mojolicious::Lite -signatures, -async; + + # Request HTML titles from two sites non-blocking + get '/' => async sub ($c) { + my $mojo_tx = await $c->ua->get_p('https://mojolicious.org'); + my $mojo_title = $mojo_tx->result->dom->at('title')->text; + my $cpan_tx = await $c->ua->get_p('https://metacpan.org'); + my $cpan_title = $cpan_tx->result->dom->at('title')->text; + + $c->render(json => {mojo => $mojo_title, cpan => $cpan_title}); + }; + + app->start; + +Promises returned by actions will automatically get the default L<Mojolicious> +exception handler attached. Making it much harder to ever miss a non-blocking +exception again, even if you forgot to handle it yourself. + =head2 Timers Timers, another primary feature of the event loop, are created with diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/lib/Mojolicious.pm new/Mojolicious-8.29/lib/Mojolicious.pm --- old/Mojolicious-8.27/lib/Mojolicious.pm 2019-11-25 22:39:44.000000000 +0100 +++ new/Mojolicious-8.29/lib/Mojolicious.pm 2019-12-28 00:28:28.000000000 +0100 @@ -59,7 +59,7 @@ has validator => sub { Mojolicious::Validator->new }; our $CODENAME = 'Supervillain'; -our $VERSION = '8.27'; +our $VERSION = '8.29'; sub BUILD_DYNAMIC { my ($class, $method, $dyn_methods) = @_; @@ -137,7 +137,7 @@ # Dispatcher has to be last in the chain ++$self->{dispatch} - and $self->hook(around_action => sub { $_[2]($_[1]) }) + and $self->hook(around_action => \&_action) and $self->hook(around_dispatch => sub { $_[1]->app->dispatch($_[1]) }) unless $self->{dispatch}; @@ -197,6 +197,14 @@ sub startup { } +sub _action { + my ($next, $c, $action, $last) = @_; + my $val = $action->($c); + $val->catch(sub { $c->helpers->reply->exception(shift) }) + if Scalar::Util::blessed $val && $val->isa('Mojo::Promise'); + return $val; +} + sub _die { CORE::die ref $_[0] ? $_[0] : Mojo::Exception->new(shift)->trace } sub _exception { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/t/mojo/promise.t new/Mojolicious-8.29/t/mojo/promise.t --- old/Mojolicious-8.27/t/mojo/promise.t 2019-11-25 22:40:21.000000000 +0100 +++ new/Mojolicious-8.29/t/mojo/promise.t 2019-12-24 01:09:02.000000000 +0100 @@ -142,7 +142,7 @@ $promise = Mojo::Promise->new; @results = (); $promise->finally(sub { push @results, 'finally1' }) - ->finally(sub { push @results, 'finally2' }); + ->finally(sub { push @results, 'finally2' }); $promise->resolve('pass'); Mojo::IOLoop->one_tick; is_deeply \@results, ['finally1', 'finally2'], 'promise not resolved'; @@ -197,7 +197,7 @@ $promise3->resolve('third'); $promise->resolve('first'); Mojo::IOLoop->one_tick; -is_deeply \@results, ['second'], 'promises resolved'; +is_deeply \@results, ['second'], 'promise resolved'; # Rejected race $promise = Mojo::Promise->new->then(sub {@_}); @@ -213,6 +213,32 @@ is_deeply \@results, [], 'promises not resolved'; is_deeply \@errors, ['second'], 'promise rejected'; +# Any +$promise = Mojo::Promise->new->then(sub {@_}); +$promise2 = Mojo::Promise->new->then(sub {@_}); +$promise3 = Mojo::Promise->new->then(sub {@_}); +@results = (); +Mojo::Promise->any($promise2, $promise, $promise3)->then(sub { @results = @_ }); +$promise2->reject('second'); +$promise3->resolve('third'); +$promise->resolve('first'); +Mojo::IOLoop->one_tick; +is_deeply \@results, ['third'], 'promise resolved'; + +# Any (all rejections) +$promise = Mojo::Promise->new->then(sub {@_}); +$promise2 = Mojo::Promise->new->then(sub {@_}); +$promise3 = Mojo::Promise->new->then(sub {@_}); +(@results, @errors) = (); +Mojo::Promise->any($promise, $promise2, $promise3) + ->then(sub { @results = @_ }, sub { @errors = @_ }); +$promise2->reject('second'); +$promise3->reject('third'); +$promise->reject('first'); +Mojo::IOLoop->one_tick; +is_deeply \@results, [], 'promises not resolved'; +is_deeply \@errors, [['first'], ['second'], ['third']], 'promises rejected'; + # Timeout (@errors, @results) = @_; $promise = Mojo::Promise->timeout(0.25 => 'Timeout1'); @@ -265,6 +291,43 @@ is_deeply \@results, [], 'promises not resolved'; is_deeply \@errors, ['third'], 'promise rejected'; +# All settled +$promise = Mojo::Promise->new->then(sub {@_}); +$promise2 = Mojo::Promise->new->then(sub {@_}); +$promise3 = Mojo::Promise->new->then(sub {@_}); +@results = (); +Mojo::Promise->all_settled($promise, $promise2, $promise3) + ->then(sub { @results = @_ }); +$promise2->resolve('second'); +$promise3->resolve('third'); +$promise->resolve('first'); +Mojo::IOLoop->one_tick; +my $result = [ + {status => 'fulfilled', value => ['first']}, + {status => 'fulfilled', value => ['second']}, + {status => 'fulfilled', value => ['third']} +]; +is_deeply \@results, $result, 'promise resolved'; + +# All settled (with rejection) +$promise = Mojo::Promise->new->then(sub {@_}); +$promise2 = Mojo::Promise->new->then(sub {@_}); +$promise3 = Mojo::Promise->new->then(sub {@_}); +(@results, @errors) = (); +Mojo::Promise->all_settled($promise, $promise2, $promise3) + ->then(sub { @results = @_ }, sub { @errors = @_ }); +$promise2->resolve('second'); +$promise3->reject('third'); +$promise->resolve('first'); +Mojo::IOLoop->one_tick; +is_deeply \@errors, [], 'promise not rejected'; +$result = [ + {status => 'fulfilled', value => ['first']}, + {status => 'fulfilled', value => ['second']}, + {status => 'rejected', reason => ['third']} +]; +is_deeply \@results, $result, 'promise resolved'; + # Settle with promise $promise = Mojo::Promise->new->resolve('works'); @results = (); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/t/mojo/promise_async_await.t new/Mojolicious-8.29/t/mojo/promise_async_await.t --- old/Mojolicious-8.27/t/mojo/promise_async_await.t 1970-01-01 01:00:00.000000000 +0100 +++ new/Mojolicious-8.29/t/mojo/promise_async_await.t 2019-12-28 17:56:17.000000000 +0100 @@ -0,0 +1,139 @@ +use Mojo::Base -strict; + +BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } + +use Test::More; + +BEGIN { + plan skip_all => 'set TEST_ASYNC_AWAIT to enable this test (developer only!)' + unless $ENV{TEST_ASYNC_AWAIT} || $ENV{TEST_ALL}; + plan skip_all => 'Future::AsyncAwait::Frozen 0.36+ required for this test!' + unless Mojo::Base->ASYNC; +} +use Mojo::Base -async; + +use Test::Mojo; +use Mojo::Promise; +use Mojo::UserAgent; +use Mojolicious::Lite; + +# Silence +app->log->level('fatal'); + +helper defer_resolve_p => sub { + my ($c, $msg) = @_; + my $promise = Mojo::Promise->new; + Mojo::IOLoop->next_tick(sub { $promise->resolve($msg) }); + return $promise; +}; + +helper defer_reject_p => sub { + my ($c, $msg) = @_; + my $promise = Mojo::Promise->new; + Mojo::IOLoop->next_tick(sub { $promise->reject($msg) }); + return $promise; +}; + +get '/one' => {text => 'works!'}; + +get '/two' => {text => 'also'}; + +get '/three' => async sub { + my $c = shift; + my $first = await $c->defer_resolve_p('this '); + my $second = await $c->defer_resolve_p('works'); + my $third = await $c->defer_resolve_p(' too!'); + $c->render(text => "$first$second$third"); +}; + +get '/four' => async sub { + my $c = shift; + + my $text = await Mojo::Promise->resolve('fail'); + eval { await $c->defer_reject_p('this went perfectly') }; + if (my $err = $@) { $c->render(text => $err, status => 500) } + else { $c->render(text => $text) } +}; + +get '/five' => async sub { + my $c = shift; + my $runaway = $c->defer_reject_p('runaway too'); + await $c->defer_resolve_p('fail'); + await $runaway; +}; + +get '/six' => sub { + my $c = shift; + $c->on( + message => async sub { + my ($c, $msg) = @_; + my $first = await $c->defer_resolve_p("One: $msg"); + my $second = await $c->defer_resolve_p("Two: $msg"); + $c->send("$first $second")->finish; + } + ); +}; + +my $ua = Mojo::UserAgent->new(ioloop => Mojo::IOLoop->singleton); + +async sub test_one { + await $ua->get_p('/one'); +} + +async sub test_two { + my $separator = shift; + + my $text = ''; + my $two = await $ua->get_p('/two'); + $text .= $two->res->body; + my $one = await $ua->get_p('/one'); + $text .= $separator . $one->res->body; + + return $text; +} + +async sub test_three { + my $ok = shift; + return Mojo::Promise->new(sub { + my ($resolve, $reject) = @_; + Mojo::IOLoop->next_tick(sub { ($ok ? $resolve : $reject)->('value') }); + }); +} + +my $t = Test::Mojo->new; + +# Basic async/await +my $promise = test_one(); +isa_ok $promise, 'Mojo::Promise', 'right class'; +my $tx; +$promise->then(sub { $tx = shift })->catch(sub { warn @_ }); +$promise->wait; +is $tx->res->body, 'works!', 'right content'; + +# Multiple awaits +my $text; +test_two(' ')->then(sub { $text = shift })->catch(sub { warn @_ })->wait; +is $text, 'also works!', 'right content'; + +# Application with async/await action +$t->get_ok('/three')->content_is('this works too!'); + +# Exception handling and async/await +$t->get_ok('/four')->status_is(500)->content_like(qr/this went perfectly/); + +# Runaway exception +$t->get_ok('/five')->status_is(500)->content_like(qr/runaway too/); + +# Async function body returning a promise +$text = undef; +test_three(1)->then(sub { $text = shift })->catch(sub { warn @_ })->wait; +is $text, 'value', 'right content'; +$text = undef; +test_three(0)->then(sub { warn @_ })->catch(sub { $text = shift })->wait; +is $text, 'value', 'right content'; + +# Async WebSocket +$t->websocket_ok('/six')->send_ok('test') + ->message_ok->message_is('One: test Two: test')->finish_ok; + +done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Mojolicious-8.27/t/pod_coverage.t new/Mojolicious-8.29/t/pod_coverage.t --- old/Mojolicious-8.27/t/pod_coverage.t 2019-11-25 22:40:27.000000000 +0100 +++ new/Mojolicious-8.29/t/pod_coverage.t 2019-12-26 16:13:28.000000000 +0100 @@ -7,4 +7,11 @@ plan skip_all => 'Test::Pod::Coverage 1.04+ required for this test!' unless eval 'use Test::Pod::Coverage 1.04; 1'; -all_pod_coverage_ok({also_private => ['BUILD_DYNAMIC', 'success']}); +# async/await hooks +my @await = ( + qw(AWAIT_CLONE AWAIT_DONE AWAIT_FAIL AWAIT_GET AWAIT_IS_CANCELLED), + qw(AWAIT_IS_READY AWAIT_NEW_DONE AWAIT_NEW_FAIL AWAIT_ON_CANCEL), + qw(AWAIT_ON_READY) +); + +all_pod_coverage_ok({also_private => ['BUILD_DYNAMIC', @await, 'success']});
participants (1)
-
root