AsyncAwait.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. package Mojo::AsyncAwait;
  2. use Mojo::Base -strict;
  3. use Carp ();
  4. use Coro::State ();
  5. use Mojo::Util;
  6. use Mojo::Promise;
  7. use Scalar::Util ();
  8. use Sub::Util ();
  9. use Exporter 'import';
  10. our @EXPORT = (qw/async await/);
  11. my $main = Coro::State->new;
  12. $main->{desc} = 'Mojo::AsyncAwait/$main';
  13. # LIFO stack of coroutines waiting to come back to
  14. # always has $main as the bottom of the stack
  15. my @stack = ($main);
  16. # Coroutines that are ostensible done but need someone to kill them
  17. my @clean;
  18. # _push adds a coroutine to the stack and enters it
  19. # when control returns to the original pusher, it will clean up
  20. # any coroutines that are waiting to be cleaned up
  21. sub _push {
  22. push @stack, @_;
  23. $stack[-2]->transfer($stack[-1]);
  24. $_->cancel for @clean;
  25. @clean = ();
  26. }
  27. # _pop pops the current coroutine off the stack. If given a callback, it calls
  28. # a callback on it, otherwise, schedules it for cleanup. It then transfers to
  29. # the next one on the stack. Note that it can't pop-and-return (which would
  30. # make more sense) because any action on it must happen before control is
  31. # transfered away from it
  32. sub _pop (;&) {
  33. Carp::croak "Cannot leave the main thread"
  34. if $stack[-1] == $main;
  35. my ($cb) = @_;
  36. my $current = pop @stack;
  37. if ($cb) { $cb->($current) }
  38. else { push @clean, $current }
  39. $current->transfer($stack[-1]);
  40. }
  41. sub async {
  42. my $body = pop;
  43. my $opts = _parse_opts(@_);
  44. my @caller = caller;
  45. my $subname = "$caller[0]::__ASYNCSUB__";
  46. my $bodyname = "$caller[0]::__ASYNCBODY__";
  47. if (defined(my $name = $opts->{-name})) {
  48. $subname = $opts->{-install} ? "$caller[0]::$name" : "$subname($name)";
  49. $bodyname .= "($name)";
  50. }
  51. my $desc = "declared at $caller[1] line $caller[2]";
  52. Sub::Util::set_subname($bodyname => $body)
  53. if Sub::Util::subname($body) =~ /::__ANON__$/;
  54. my $wrapped = sub {
  55. my @caller = caller;
  56. my $promise = Mojo::Promise->new;
  57. my $coro = Coro::State->new(sub {
  58. eval {
  59. BEGIN { $^H{'Mojo::AsyncAwait/async'} = 1 }
  60. $promise->resolve($body->(@_)); 1
  61. } or $promise->reject($@);
  62. _pop;
  63. }, @_);
  64. $coro->{desc} = "$subname called at $caller[1] line $caller[2], $desc";
  65. _push $coro;
  66. return $promise;
  67. };
  68. if ($opts->{-install}) {
  69. Mojo::Util::monkey_patch $caller[0], $opts->{-name} => $wrapped;
  70. return;
  71. }
  72. Sub::Util::set_subname $subname => $wrapped;
  73. return $wrapped;
  74. }
  75. # this prototype prevents the perl tokenizer from seeing await as an
  76. # indirect method
  77. sub await (*) {
  78. {
  79. # check that our caller is actually an async function
  80. no warnings 'uninitialized';
  81. my $level = 1;
  82. my ($caller, $hints) = (caller($level))[3, 10];
  83. # being inside of an eval is ok too
  84. ($caller, $hints) = (caller(++$level))[3, 10] while $caller eq '(eval)';
  85. Carp::croak 'await may only be called from in async function'
  86. unless $hints->{'Mojo::AsyncAwait/async'};
  87. }
  88. my $promise = shift;
  89. $promise = Mojo::Promise->new->resolve($promise)
  90. unless Scalar::Util::blessed($promise) && $promise->can('then');
  91. my (@retvals, $err);
  92. _pop {
  93. my $current = shift;
  94. $promise->then(
  95. sub {
  96. @retvals = @_;
  97. _push $current;
  98. },
  99. sub {
  100. $err = shift;
  101. _push $current;
  102. }
  103. );
  104. };
  105. # "_push $current" in the above callback brings us here
  106. Carp::croak($err) if $err;
  107. return wantarray ? @retvals : $retvals[0];
  108. }
  109. sub _parse_opts {
  110. return {} unless @_;
  111. return {
  112. -name => shift,
  113. -install => 1,
  114. } if @_ == 1;
  115. my %opts = @_;
  116. Carp::croak 'Cannot install a sub without a name'
  117. if $opts{-install} && !defined $opts{-name};
  118. return \%opts;
  119. }
  120. 1;
  121. =encoding utf8
  122. =head1 NAME
  123. Mojo::AsyncAwait - An Async/Await implementation for Mojolicious
  124. =head1 SYNOPSIS
  125. use Mojolicious::Lite -signatures;
  126. use Mojo::AsyncAwait;
  127. get '/' => async sub ($c) {
  128. my $mojo = await $c->ua->get_p('https://mojolicious.org');
  129. my $cpan = await $c->ua->get_p('https://metacpan.org');
  130. $c->render(json => {
  131. mojo => $mojo->result->code,
  132. cpan => $cpan->result->code
  133. });
  134. };
  135. app->start;
  136. =head1 DESCRIPTION
  137. Async/await is a language-independent pattern that allows nonblocking
  138. asynchronous code to be structured simliarly to blocking code. This is done by
  139. allowing execution to be suspended by the await keyword and returning once the
  140. promise passed to await has been fulfilled.
  141. This pattern simplies the use of both promises and nonblocking code in general
  142. and is therefore a very exciting development for writing asynchronous systems.
  143. If you are going to use this module to create async controllers actions in
  144. L<Mojolicious> applications (as seen in the L</SYNOPSIS>), you are highly
  145. encouraged to also use L<Mojolicious::Plugin::PromiseActions> in order to
  146. properly handle exceptions in your action.
  147. =head1 GOALS
  148. The primary goal of this module is to provide a useful Async/Await
  149. implementation for users of the Mojolicious ecosystem. It is for this reason
  150. that L<Mojo::Promise> is used when new promises are created. Because this is
  151. the primary goal, the intention is for it to remain useful even as other goals
  152. are considered.
  153. Secondarily, it is intended to be a testbed for early implementations of
  154. Async/Await in the Perl 5 language. It is for this reason that the
  155. implementation details are intended to be replaceable. This may manifest as a
  156. pluggable backend or rather as wholesale rewrites of the internals. The result
  157. should hopefully be backwards compatible, mostly because the interface is so
  158. simple, just two keywords.
  159. Of course, I always intend as much as possible that Mojolicious-focused code is
  160. as useful as practically possible for the broader Perl 5 ecosystem. It is for
  161. this reason that while this module returns L<Mojo::Promise>s, it can accept any
  162. then-able (read: promise) which conforms enough to the Promises/A+ standard.
  163. The Promises/A+ standard is intended to increase the interoperability of
  164. promises, and while that line becomes more gray in Perl 5 where we don't have a
  165. single ioloop implementation, we try our best.
  166. As implementations stabilze, or change, certain portions may be spun off. The
  167. initial implementation depends on L<Coro>. Should that change, or should users
  168. want to use it with other promise implementations, perhaps that implementation
  169. will be spun off to be used apart from L<Mojolicious> and/or L<Mojo::Promise>,
  170. perhaps not.
  171. Finally the third goal is to improve the mobility of the knowledge of this
  172. pattern between languages. Users of Javascript probably are already familiar
  173. with this patthern; when coming to Perl 5 they will want to continue to use it.
  174. Likewise, as Perl 5 users take on new languages, if they are familiar with
  175. common patterns in their new language, they will have an easier time learning.
  176. Having a useable Async/Await library in Perl 5 is key to keeping Perl 5
  177. relevent in moderning coding.
  178. =head1 CAVEATS
  179. First and foremost, this is all a little bit crazy. Please consider carefully
  180. before using this code in production.
  181. While many languages have async/await as a core language feature, currently in
  182. Perl we must rely on modules that provide the mechanism of suspending and
  183. resuming execution.
  184. The default implementation relies on L<Coro> which does some very magical
  185. things to the Perl interpreter. Other less magical implementations are in the
  186. works however none are available yet. In the future if additional
  187. implementations are available, this module might well be made pluggable. Please
  188. do not rely on L<Coro> being the implmementation of choice.
  189. Also note that while a L<Coro>-based implementation need not rely on L</await>
  190. being called directly from an L</async> function, it is currently prohibitied
  191. because it is likely that other/future implementations will rely on that
  192. behavior and thus it should not be relied upon.
  193. =head1 KEYWORDS
  194. L<Mojo::AsyncAwait> provides two keywords (i.e. functions), both exported by
  195. default.
  196. =head2 async
  197. my $sub = async sub { ... };
  198. The async keyword wraps a subroutine as an asynchronous subroutine which is
  199. able to be suspended via L</await>. The return value(s) of the subroutine, when
  200. called, will be wrapped in a L<Mojo::Promise>.
  201. The async keyword must be called with a subroutine reference, which will be the
  202. body of the async subroutine.
  203. Note that the returned subroutine reference is not invoked for you.
  204. If you want to immediately invoke it, you need to so manually.
  205. my $promise = async(sub{ ... })->();
  206. If called with a preceding name, the subroutine will be installed into the current package with that name.
  207. async installed_sub => sub { ... };
  208. installed_sub();
  209. If called with key-value arguments starting with a dash, the following options are available.
  210. =over
  211. =item -install
  212. If set to a true value, the subroutine will be installed into the current package.
  213. Default is false.
  214. Setting this value to true without a C<-name> is an error.
  215. =item -name
  216. If C<-install> is false, this is a diagnostic name to be included in the subname for debugging purposes.
  217. This name is seen in diagnostic information, like stack traces.
  218. my $named_sub = async -name => my_name => sub { ... };
  219. $named_sub->();
  220. Otherwise this is the name that will be installed into the current package.
  221. =back
  222. Therefore, passing a bare name as is identical to setting both C<-name> and C<< -install => 1 >>.
  223. async -name => installed_sub, -install => 1 => sub { ... };
  224. installed_sub();
  225. If the subroutine is installed, whether by passing a bare name or the C<-install> option, nothing is returned.
  226. Otherwise the return value is the wrapped async subroutine reference.
  227. =head2 await
  228. my $tx = await Mojo::UserAgent->new->get_p('https://mojolicious.org');
  229. my @results = await (async sub { ...; return @async_results })->();
  230. The await keyword suspends execution of an async sub until a promise is
  231. fulfilled, returning the promise's results. In list context all promise results
  232. are returned. For ease of use, in scalar context the first promise result is
  233. returned and the remainder are discarded.
  234. If the value passed to await is not a promise (defined as having a C<then>
  235. method>), it will be wrapped in a Mojo::Promise for consistency. This is mostly
  236. inconsequential to the user.
  237. Note that await can only take one promise as an argument. If you wanted to
  238. await multiple promises you probably want L<Mojo::Promise/all> or less likely
  239. L<Mojo::Promise/race>.
  240. my $results = await Mojo::Promise->all(@promises);
  241. =head1 AUTHORS
  242. Joel Berger <joel.a.berger@gmail.com>
  243. Marcus Ramberg <mramberg@cpan.org>
  244. =head1 CONTRIBUTORS
  245. Sebastian Riedel <kraih@mojolicious.org>
  246. =head1 ADDITIONAL THANKS
  247. Matt S Trout (mst)
  248. Paul Evans (LeoNerd)
  249. John Susek
  250. =head1 COPYRIGHT AND LICENSE
  251. Copyright (C) 2018, L</AUTHORS> and L</CONTRIBUTORS>.
  252. This program is free software, you can redistribute it and/or modify it under
  253. the terms of the Artistic License version 2.0.
  254. =head1 SEE ALSO
  255. L<Mojo::Promise>
  256. L<Mojolicious::Plugin::PromiseActions>
  257. L<MDN Async/Await|https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function>
  258. L<Coro::State>
  259. L<Future::AsyncAwait>
  260. L<PerlX::AsyncAwait>
  261. =cut