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 - <[email protected]>
+
+- 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 <[email protected]>"
],
"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']});