| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 | package Mojo::AsyncAwait;use Mojo::Base -strict;use Carp ();use Coro::State ();use Mojo::Util;use Mojo::Promise;use Scalar::Util ();use Sub::Util ();use Exporter 'import';our @EXPORT = (qw/async await/);my $main = Coro::State->new;$main->{desc} = 'Mojo::AsyncAwait/$main';# LIFO stack of coroutines waiting to come back to# always has $main as the bottom of the stackmy @stack = ($main);# Coroutines that are ostensible done but need someone to kill themmy @clean;# _push adds a coroutine to the stack and enters it# when control returns to the original pusher, it will clean up# any coroutines that are waiting to be cleaned upsub _push {  push @stack, @_;  $stack[-2]->transfer($stack[-1]);  $_->cancel for @clean;  @clean = ();}# _pop pops the current coroutine off the stack. If given a callback, it calls# a callback on it, otherwise, schedules it for cleanup. It then transfers to# the next one on the stack. Note that it can't pop-and-return (which would# make more sense) because any action on it must happen before control is# transfered away from itsub _pop (;&) {  Carp::croak "Cannot leave the main thread"    if $stack[-1] == $main;  my ($cb) = @_;  my $current = pop @stack;  if ($cb) { $cb->($current)       }  else     { push @clean, $current }  $current->transfer($stack[-1]);}sub async {  my $body   = pop;  my $opts   = _parse_opts(@_);  my @caller = caller;  my $subname  = "$caller[0]::__ASYNCSUB__";  my $bodyname = "$caller[0]::__ASYNCBODY__";  if (defined(my $name = $opts->{-name})) {    $subname  = $opts->{-install} ? "$caller[0]::$name" : "$subname($name)";    $bodyname .= "($name)";  }  my $desc = "declared at $caller[1] line $caller[2]";  Sub::Util::set_subname($bodyname => $body)    if Sub::Util::subname($body) =~ /::__ANON__$/;  my $wrapped = sub {    my @caller  = caller;    my $promise = Mojo::Promise->new;    my $coro    = Coro::State->new(sub {      eval {        BEGIN { $^H{'Mojo::AsyncAwait/async'} = 1 }        $promise->resolve($body->(@_)); 1      } or $promise->reject($@);      _pop;    }, @_);    $coro->{desc} = "$subname called at $caller[1] line $caller[2], $desc";    _push $coro;    return $promise;  };  if ($opts->{-install}) {    Mojo::Util::monkey_patch $caller[0], $opts->{-name} => $wrapped;    return;  }  Sub::Util::set_subname $subname => $wrapped;  return $wrapped;}# this prototype prevents the perl tokenizer from seeing await as an# indirect methodsub await (*) {  {    # check that our caller is actually an async function    no warnings 'uninitialized';    my $level = 1;    my ($caller, $hints) = (caller($level))[3, 10];    # being inside of an eval is ok too    ($caller, $hints) = (caller(++$level))[3, 10] while $caller eq '(eval)';    Carp::croak 'await may only be called from in async function'      unless $hints->{'Mojo::AsyncAwait/async'};  }  my $promise = shift;  $promise = Mojo::Promise->new->resolve($promise)    unless Scalar::Util::blessed($promise) && $promise->can('then');  my (@retvals, $err);  _pop {    my $current = shift;    $promise->then(      sub {        @retvals = @_;        _push $current;      },      sub {        $err = shift;        _push $current;      }    );  };  # "_push $current" in the above callback brings us here  Carp::croak($err) if $err;  return wantarray ? @retvals : $retvals[0];}sub _parse_opts {  return {} unless @_;  return {    -name    => shift,    -install => 1,  } if @_ == 1;  my %opts = @_;  Carp::croak 'Cannot install a sub without a name'    if $opts{-install} && !defined $opts{-name};  return \%opts;}1;=encoding utf8=head1 NAMEMojo::AsyncAwait - An Async/Await implementation for Mojolicious=head1 SYNOPSIS  use Mojolicious::Lite -signatures;  use Mojo::AsyncAwait;  get '/' => async sub ($c) {    my $mojo = await $c->ua->get_p('https://mojolicious.org');    my $cpan = await $c->ua->get_p('https://metacpan.org');    $c->render(json => {      mojo => $mojo->result->code,      cpan => $cpan->result->code    });  };  app->start;=head1 DESCRIPTIONAsync/await is a language-independent pattern that allows nonblockingasynchronous code to be structured simliarly to blocking code. This is done byallowing execution to be suspended by the await keyword and returning once thepromise passed to await has been fulfilled.This pattern simplies the use of both promises and nonblocking code in generaland is therefore a very exciting development for writing asynchronous systems.If you are going to use this module to create async controllers actions inL<Mojolicious> applications (as seen in the L</SYNOPSIS>), you are highlyencouraged to also use L<Mojolicious::Plugin::PromiseActions> in order toproperly handle exceptions in your action.=head1 GOALSThe primary goal of this module is to provide a useful Async/Awaitimplementation for users of the Mojolicious ecosystem. It is for this reasonthat L<Mojo::Promise> is used when new promises are created. Because this isthe primary goal, the intention is for it to remain useful even as other goalsare considered.Secondarily, it is intended to be a testbed for early implementations ofAsync/Await in the Perl 5 language. It is for this reason that theimplementation details are intended to be replaceable. This may manifest as apluggable backend or rather as wholesale rewrites of the internals. The resultshould hopefully be backwards compatible, mostly because the interface is sosimple, just two keywords.Of course, I always intend as much as possible that Mojolicious-focused code isas useful as practically possible for the broader Perl 5 ecosystem. It is forthis reason that while this module returns L<Mojo::Promise>s, it can accept anythen-able (read: promise) which conforms enough to the Promises/A+ standard.The Promises/A+ standard is intended to increase the interoperability ofpromises, and while that line becomes more gray in Perl 5 where we don't have asingle ioloop implementation, we try our best.As implementations stabilze, or change, certain portions may be spun off. Theinitial implementation depends on L<Coro>. Should that change, or should userswant to use it with other promise implementations, perhaps that implementationwill be spun off to be used apart from L<Mojolicious> and/or L<Mojo::Promise>,perhaps not.Finally the third goal is to improve the mobility of the knowledge of thispattern between languages. Users of Javascript probably are already familiarwith this patthern; when coming to Perl 5 they will want to continue to use it.Likewise, as Perl 5 users take on new languages, if they are familiar withcommon patterns in their new language, they will have an easier time learning.Having a useable Async/Await library in Perl 5 is key to keeping Perl 5relevent in moderning coding.=head1 CAVEATSFirst and foremost, this is all a little bit crazy. Please consider carefullybefore using this code in production.While many languages have async/await as a core language feature, currently inPerl we must rely on modules that provide the mechanism of suspending andresuming execution.The default implementation relies on L<Coro> which does some very magicalthings to the Perl interpreter. Other less magical implementations are in theworks however none are available yet. In the future if additionalimplementations are available, this module might well be made pluggable. Pleasedo not rely on L<Coro> being the implmementation of choice.Also note that while a L<Coro>-based implementation need not rely on L</await>being called directly from an L</async> function, it is currently prohibitiedbecause it is likely that other/future implementations will rely on thatbehavior and thus it should not be relied upon.=head1 KEYWORDSL<Mojo::AsyncAwait> provides two keywords (i.e. functions), both exported bydefault.=head2 async  my $sub = async sub { ... };The async keyword wraps a subroutine as an asynchronous subroutine which isable to be suspended via L</await>. The return value(s) of the subroutine, whencalled, will be wrapped in a L<Mojo::Promise>.The async keyword must be called with a subroutine reference, which will be thebody of the async subroutine.Note that the returned subroutine reference is not invoked for you.If you want to immediately invoke it, you need to so manually.  my $promise = async(sub{ ... })->();If called with a preceding name, the subroutine will be installed into the current package with that name.  async installed_sub => sub { ... };  installed_sub();If called with key-value arguments starting with a dash, the following options are available.=over=item -installIf set to a true value, the subroutine will be installed into the current package.Default is false.Setting this value to true without a C<-name> is an error.=item -nameIf C<-install> is false, this is a diagnostic name to be included in the subname for debugging purposes.This name is seen in diagnostic information, like stack traces.  my $named_sub = async -name => my_name => sub { ... };  $named_sub->();Otherwise this is the name that will be installed into the current package.=backTherefore, passing a bare name as is identical to setting both C<-name> and C<< -install => 1 >>.  async -name => installed_sub, -install => 1 => sub { ... };  installed_sub();If the subroutine is installed, whether by passing a bare name or the C<-install> option, nothing is returned.Otherwise the return value is the wrapped async subroutine reference.=head2 await  my $tx = await Mojo::UserAgent->new->get_p('https://mojolicious.org');  my @results = await (async sub { ...; return @async_results })->();The await keyword suspends execution of an async sub until a promise isfulfilled, returning the promise's results. In list context all promise resultsare returned. For ease of use, in scalar context the first promise result isreturned and the remainder are discarded.If the value passed to await is not a promise (defined as having a C<then>method>), it will be wrapped in a Mojo::Promise for consistency. This is mostlyinconsequential to the user.Note that await can only take one promise as an argument. If you wanted toawait multiple promises you probably want L<Mojo::Promise/all> or less likelyL<Mojo::Promise/race>.  my $results = await Mojo::Promise->all(@promises);=head1 AUTHORSJoel Berger <joel.a.berger@gmail.com>Marcus Ramberg <mramberg@cpan.org>=head1 CONTRIBUTORSSebastian Riedel <kraih@mojolicious.org>=head1 ADDITIONAL THANKSMatt S Trout (mst)Paul Evans (LeoNerd)John Susek=head1 COPYRIGHT AND LICENSECopyright (C) 2018, L</AUTHORS> and L</CONTRIBUTORS>.This program is free software, you can redistribute it and/or modify it underthe terms of the Artistic License version 2.0.=head1 SEE ALSOL<Mojo::Promise>L<Mojolicious::Plugin::PromiseActions>L<MDN Async/Await|https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function>L<Coro::State>L<Future::AsyncAwait>L<PerlX::AsyncAwait>=cut
 |