├── .gitattributes ├── .perltidyrc ├── Changes ├── LICENSE ├── MANIFEST.SKIP ├── README.md ├── cpanfile ├── lib └── Mojo │ ├── AsyncAwait.pm │ └── AsyncAwait │ └── Backend │ └── Coro.pm ├── metamerge.json └── t ├── app.t ├── app_all.t ├── await_async.t ├── basic.t ├── exceptions.t ├── installed.t ├── lib └── TestHelper.pm ├── named.t ├── promise_actions.t └── scalar.t /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pl linguist-language=Perl 2 | *.pm linguist-language=Perl 3 | *.t linguist-language=Perl 4 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | -pbp # Start with Perl Best Practices 2 | -w # Show all warnings 3 | -iob # Ignore old breakpoints 4 | -l=80 # 80 characters per line 5 | -mbl=2 # No more than 2 blank lines 6 | -i=2 # Indentation is 2 columns 7 | -ci=2 # Continuation indentation is 2 columns 8 | -vt=0 # Less vertical tightness 9 | -pt=2 # High parenthesis tightness 10 | -bt=2 # High brace tightness 11 | -sbt=2 # High square bracket tightness 12 | -wn # Weld nested containers 13 | -isbc # Don't indent comments without leading space 14 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | 0.03 2020-01-15 2 | - Fix the test that was broken on Mojo <= 8.27 (sigh) 3 | 4 | 0.02 2020-01-14 5 | - PromiseActions isn't needed as of Mojo 8.28 6 | 7 | 0.01 2018-11-17 8 | - Initial release. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is Copyright (c) 2020 by Joel Berger , Marcus Ramberg . 2 | 3 | This is free software, licensed under: 4 | 5 | The Artistic License 2.0 (GPL Compatible) 6 | 7 | The Artistic License 2.0 8 | 9 | Copyright (c) 2000-2006, The Perl Foundation. 10 | 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | 14 | Preamble 15 | 16 | This license establishes the terms under which a given free software 17 | Package may be copied, modified, distributed, and/or redistributed. 18 | The intent is that the Copyright Holder maintains some artistic 19 | control over the development of that Package while still keeping the 20 | Package available as open source and free software. 21 | 22 | You are always permitted to make arrangements wholly outside of this 23 | license directly with the Copyright Holder of a given Package. If the 24 | terms of this license do not permit the full use that you propose to 25 | make of the Package, you should contact the Copyright Holder and seek 26 | a different licensing arrangement. 27 | 28 | Definitions 29 | 30 | "Copyright Holder" means the individual(s) or organization(s) 31 | named in the copyright notice for the entire Package. 32 | 33 | "Contributor" means any party that has contributed code or other 34 | material to the Package, in accordance with the Copyright Holder's 35 | procedures. 36 | 37 | "You" and "your" means any person who would like to copy, 38 | distribute, or modify the Package. 39 | 40 | "Package" means the collection of files distributed by the 41 | Copyright Holder, and derivatives of that collection and/or of 42 | those files. A given Package may consist of either the Standard 43 | Version, or a Modified Version. 44 | 45 | "Distribute" means providing a copy of the Package or making it 46 | accessible to anyone else, or in the case of a company or 47 | organization, to others outside of your company or organization. 48 | 49 | "Distributor Fee" means any fee that you charge for Distributing 50 | this Package or providing support for this Package to another 51 | party. It does not mean licensing fees. 52 | 53 | "Standard Version" refers to the Package if it has not been 54 | modified, or has been modified only in ways explicitly requested 55 | by the Copyright Holder. 56 | 57 | "Modified Version" means the Package, if it has been changed, and 58 | such changes were not explicitly requested by the Copyright 59 | Holder. 60 | 61 | "Original License" means this Artistic License as Distributed with 62 | the Standard Version of the Package, in its current version or as 63 | it may be modified by The Perl Foundation in the future. 64 | 65 | "Source" form means the source code, documentation source, and 66 | configuration files for the Package. 67 | 68 | "Compiled" form means the compiled bytecode, object code, binary, 69 | or any other form resulting from mechanical transformation or 70 | translation of the Source form. 71 | 72 | 73 | Permission for Use and Modification Without Distribution 74 | 75 | (1) You are permitted to use the Standard Version and create and use 76 | Modified Versions for any purpose without restriction, provided that 77 | you do not Distribute the Modified Version. 78 | 79 | 80 | Permissions for Redistribution of the Standard Version 81 | 82 | (2) You may Distribute verbatim copies of the Source form of the 83 | Standard Version of this Package in any medium without restriction, 84 | either gratis or for a Distributor Fee, provided that you duplicate 85 | all of the original copyright notices and associated disclaimers. At 86 | your discretion, such verbatim copies may or may not include a 87 | Compiled form of the Package. 88 | 89 | (3) You may apply any bug fixes, portability changes, and other 90 | modifications made available from the Copyright Holder. The resulting 91 | Package will still be considered the Standard Version, and as such 92 | will be subject to the Original License. 93 | 94 | 95 | Distribution of Modified Versions of the Package as Source 96 | 97 | (4) You may Distribute your Modified Version as Source (either gratis 98 | or for a Distributor Fee, and with or without a Compiled form of the 99 | Modified Version) provided that you clearly document how it differs 100 | from the Standard Version, including, but not limited to, documenting 101 | any non-standard features, executables, or modules, and provided that 102 | you do at least ONE of the following: 103 | 104 | (a) make the Modified Version available to the Copyright Holder 105 | of the Standard Version, under the Original License, so that the 106 | Copyright Holder may include your modifications in the Standard 107 | Version. 108 | 109 | (b) ensure that installation of your Modified Version does not 110 | prevent the user installing or running the Standard Version. In 111 | addition, the Modified Version must bear a name that is different 112 | from the name of the Standard Version. 113 | 114 | (c) allow anyone who receives a copy of the Modified Version to 115 | make the Source form of the Modified Version available to others 116 | under 117 | 118 | (i) the Original License or 119 | 120 | (ii) a license that permits the licensee to freely copy, 121 | modify and redistribute the Modified Version using the same 122 | licensing terms that apply to the copy that the licensee 123 | received, and requires that the Source form of the Modified 124 | Version, and of any works derived from it, be made freely 125 | available in that license fees are prohibited but Distributor 126 | Fees are allowed. 127 | 128 | 129 | Distribution of Compiled Forms of the Standard Version 130 | or Modified Versions without the Source 131 | 132 | (5) You may Distribute Compiled forms of the Standard Version without 133 | the Source, provided that you include complete instructions on how to 134 | get the Source of the Standard Version. Such instructions must be 135 | valid at the time of your distribution. If these instructions, at any 136 | time while you are carrying out such distribution, become invalid, you 137 | must provide new instructions on demand or cease further distribution. 138 | If you provide valid instructions or cease distribution within thirty 139 | days after you become aware that the instructions are invalid, then 140 | you do not forfeit any of your rights under this license. 141 | 142 | (6) You may Distribute a Modified Version in Compiled form without 143 | the Source, provided that you comply with Section 4 with respect to 144 | the Source of the Modified Version. 145 | 146 | 147 | Aggregating or Linking the Package 148 | 149 | (7) You may aggregate the Package (either the Standard Version or 150 | Modified Version) with other packages and Distribute the resulting 151 | aggregation provided that you do not charge a licensing fee for the 152 | Package. Distributor Fees are permitted, and licensing fees for other 153 | components in the aggregation are permitted. The terms of this license 154 | apply to the use and Distribution of the Standard or Modified Versions 155 | as included in the aggregation. 156 | 157 | (8) You are permitted to link Modified and Standard Versions with 158 | other works, to embed the Package in a larger work of your own, or to 159 | build stand-alone binary or bytecode versions of applications that 160 | include the Package, and Distribute the result without restriction, 161 | provided the result does not expose a direct interface to the Package. 162 | 163 | 164 | Items That are Not Considered Part of a Modified Version 165 | 166 | (9) Works (including, but not limited to, modules and scripts) that 167 | merely extend or make use of the Package, do not, by themselves, cause 168 | the Package to be a Modified Version. In addition, such works are not 169 | considered parts of the Package itself, and are not subject to the 170 | terms of this license. 171 | 172 | 173 | General Provisions 174 | 175 | (10) Any use, modification, and distribution of the Standard or 176 | Modified Versions is governed by this Artistic License. By using, 177 | modifying or distributing the Package, you accept this license. Do not 178 | use, modify, or distribute the Package, if you do not accept this 179 | license. 180 | 181 | (11) If your Modified Version has been derived from a Modified 182 | Version made by someone other than you, you are nevertheless required 183 | to ensure that your Modified Version complies with the requirements of 184 | this license. 185 | 186 | (12) This license does not grant you the right to use any trademark, 187 | service mark, tradename, or logo of the Copyright Holder. 188 | 189 | (13) This license includes the non-exclusive, worldwide, 190 | free-of-charge patent license to make, have made, use, offer to sell, 191 | sell, import and otherwise transfer the Package with respect to any 192 | patent claims licensable by the Copyright Holder that are necessarily 193 | infringed by the Package. If you institute patent litigation 194 | (including a cross-claim or counterclaim) against any party alleging 195 | that the Package constitutes direct or contributory patent 196 | infringement, then this Artistic License to you shall terminate on the 197 | date that such litigation is filed. 198 | 199 | (14) Disclaimer of Warranty: 200 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 201 | IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 202 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 203 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 204 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 205 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 206 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 207 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 208 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | 2 | #!start included /Users/joel/perl5/perlbrew/perls/perl-5.20.1/lib/site_perl/5.20.1/ExtUtils/MANIFEST.SKIP 3 | # Avoid version control files. 4 | \bRCS\b 5 | \bCVS\b 6 | \bSCCS\b 7 | ,v$ 8 | \B\.svn\b 9 | \B\.git\b 10 | \B\.gitignore\b 11 | \b_darcs\b 12 | \B\.cvsignore$ 13 | 14 | # Avoid VMS specific MakeMaker generated files 15 | \bDescrip.MMS$ 16 | \bDESCRIP.MMS$ 17 | \bdescrip.mms$ 18 | 19 | # Avoid Makemaker generated and utility files. 20 | \bMANIFEST\.bak 21 | \bMakefile$ 22 | \bblib/ 23 | \bMakeMaker-\d 24 | \bpm_to_blib\.ts$ 25 | \bpm_to_blib$ 26 | \bblibdirs\.ts$ # 6.18 through 6.25 generated this 27 | 28 | # Avoid Module::Build generated and utility files. 29 | \bBuild$ 30 | \b_build/ 31 | \bBuild.bat$ 32 | \bBuild.COM$ 33 | \bBUILD.COM$ 34 | \bbuild.com$ 35 | 36 | # Avoid temp and backup files. 37 | ~$ 38 | \.old$ 39 | \#$ 40 | \b\.# 41 | \.bak$ 42 | \.tmp$ 43 | \.# 44 | \.rej$ 45 | \..*\.sw.?$ 46 | 47 | # Avoid OS-specific files/dirs 48 | # Mac OSX metadata 49 | \B\.DS_Store 50 | # Mac OSX SMB mount metadata files 51 | \B\._ 52 | 53 | # Avoid Devel::Cover and Devel::CoverX::Covered files. 54 | \bcover_db\b 55 | \bcovered\b 56 | 57 | # Avoid prove files 58 | \B\.prove$ 59 | 60 | # Avoid MYMETA files 61 | ^MYMETA\. 62 | #!end included /Users/joel/perl5/perlbrew/perls/perl-5.20.1/lib/site_perl/5.20.1/ExtUtils/MANIFEST.SKIP 63 | 64 | # Avoid configuration metadata file 65 | ^MYMETA\. 66 | 67 | # Avoid Module::Build generated and utility files. 68 | \bBuild$ 69 | \bBuild.bat$ 70 | \b_build 71 | \bBuild.COM$ 72 | \bBUILD.COM$ 73 | \bbuild.com$ 74 | ^MANIFEST\.SKIP 75 | 76 | # Avoid archives of this distribution 77 | \bMojo-AsyncAwait-[\d\.\_]+ 78 | 79 | # Avoid README.pod intended for github 80 | \bREADME\.pod$ 81 | \bREADME\.md$ 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | =encoding utf8 2 | 3 | =head1 NAME 4 | 5 | Mojo::AsyncAwait - An Async/Await implementation for Mojolicious 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Mojolicious::Lite -signatures; 10 | use Mojo::AsyncAwait; 11 | 12 | get '/' => async sub ($c) { 13 | 14 | my $mojo = await $c->ua->get_p('https://mojolicious.org'); 15 | my $cpan = await $c->ua->get_p('https://metacpan.org'); 16 | 17 | $c->render(json => { 18 | mojo => $mojo->result->code, 19 | cpan => $cpan->result->code 20 | }); 21 | }; 22 | 23 | app->start; 24 | 25 | =head1 DESCRIPTION 26 | 27 | Async/await is a language-independent pattern that allows nonblocking 28 | asynchronous code to be structured simliarly to blocking code. This is done by 29 | allowing execution to be suspended by the await keyword and returning once the 30 | promise passed to await has been fulfilled. 31 | 32 | This pattern simplies the use of both promises and nonblocking code in general 33 | and is therefore a very exciting development for writing asynchronous systems. 34 | 35 | If you are going to use this module to create async controllers actions in 36 | L applications (as seen in the L) before Mojolicious 37 | version 8.28, you are highly encouraged to also use 38 | L in order to properly handle exceptions 39 | in your action. 40 | 41 | =head1 GOALS 42 | 43 | The primary goal of this module is to provide a useful Async/Await 44 | implementation for users of the Mojolicious ecosystem. It is for this reason 45 | that L is used when new promises are created. Because this is 46 | the primary goal, the intention is for it to remain useful even as other goals 47 | are considered. 48 | 49 | Secondarily, it is intended to be a testbed for early implementations of 50 | Async/Await in the Perl 5 language. It is for this reason that the 51 | implementation details are intended to be replaceable. The result should 52 | hopefully still be backwards compatible, mostly because the interface is so 53 | simple. After all, it is just two keywords. 54 | 55 | Of course, I always intend as much as possible that Mojolicious-focused code is 56 | as useful as practically possible for the broader Perl 5 ecosystem. It is for 57 | this reason that while this module returns Ls, it can accept any 58 | then-able (read: promise) which conforms enough to the Promises/A+ standard. 59 | The Promises/A+ standard is intended to increase the interoperability of 60 | promises, and while that line becomes more gray in Perl 5 where we don't have a 61 | single ioloop implementation, we try our best. 62 | 63 | Finally the third goal is to improve the mobility of the knowledge of this 64 | pattern between languages. Users of Javascript probably are already familiar 65 | with this patthern; when coming to Perl 5 they will want to continue to use it. 66 | Likewise, as Perl 5 users take on new languages, if they are familiar with 67 | common patterns in their new language, they will have an easier time learning. 68 | Having a useable Async/Await library in Perl 5 is key to keeping Perl 5 69 | relevent in moderning coding. 70 | 71 | =head1 BACKENDS 72 | 73 | This module actually does very little on its own, it simply loads and imports 74 | backend implementations of Async/Await. The reason to use this module really 75 | would be to use current default implementation without regards to what that 76 | implementation is nor how it works. 77 | 78 | When it is loaded, the C is checked, if not set then 79 | the current default is used. 80 | 81 | # From environment 82 | BEGIN{ $ENV{MOJO_ASYNCAWAIT_BACKEND} = '+CoolBackend' } 83 | use Mojo::AsyncAwait; 84 | 85 | # Currently provided default 86 | use Mojo::AsyncAwait; 87 | 88 | The backend is specified either as a fully qualified module name, e.g. 89 | C or using the C<+> as a shortcut for 90 | C, e.g. C<+CoolBackend> which would mean exactly 91 | the same as the former. 92 | 93 | =head1 CAVEATS 94 | 95 | First and foremost, this is all a little bit crazy. Please consider carefully 96 | before using this code in production. 97 | 98 | While many languages have async/await as a core language feature, currently in 99 | Perl we must rely on modules that provide the mechanism of suspending and 100 | resuming execution. 101 | 102 | The default implementation relies on L which does some very magical 103 | things to the Perl interpreter. Other less magical implementations are in the 104 | works however none are available yet. As available implementations change or 105 | stabilize, that default may be changed. Backend implementations may be added or 106 | even be spun off. If your application depends on the backend implementation, 107 | you may import it manually or use the described mechanisms to load it. In that 108 | case you should be sure to add the backend to your dependency list in case it 109 | is spun off in the future. 110 | 111 | Also note that while a L-based implementation need not rely on L 112 | being called directly from an L function, it is currently prohibitied 113 | because it is likely that other/future implementations will rely on that 114 | behavior and thus it should not be relied upon. 115 | 116 | =head1 KEYWORDS 117 | 118 | Regardless of backend, L provides two keywords (i.e. 119 | functions), both exported by default. Depending on backend, their exact 120 | behavior might change slightly, however, implementers should attempt to follow 121 | the api described here as closely as possible. 122 | 123 | Some backends may allow additional options to be passed to the keywords; those 124 | options should be kept minimal and if possible follow the conventions described 125 | in L. This generic document will not describe 126 | those additional options. 127 | 128 | =head2 async 129 | 130 | my $sub = async sub { ... }; 131 | 132 | The async keyword wraps a subroutine as an asynchronous subroutine which is 133 | able to be suspended via L. The return value(s) of the subroutine, when 134 | called, will be wrapped in a L. 135 | 136 | The async keyword must be called with a subroutine reference, which will be the 137 | body of the async subroutine. 138 | 139 | Note that the returned subroutine reference is not invoked for you. 140 | If you want to immediately invoke it, you need to so manually. 141 | 142 | my $promise = async(sub{ ... })->(); 143 | 144 | If called with a preceding name, the subroutine will be installed into the 145 | current package with that name. 146 | 147 | async installed_sub => sub { ... }; 148 | installed_sub(); 149 | 150 | Unlike the case of an anonymous wrapped async subroutine reference described 151 | above, if the subroutine is installed, nothing is returned. 152 | 153 | =head2 await 154 | 155 | my $tx = await Mojo::UserAgent->new->get_p('https://mojolicious.org'); 156 | my @results = await (async sub { ...; return @async_results })->(); 157 | 158 | The await keyword suspends execution of an async sub until a promise is 159 | fulfilled, returning the promise's results. In list context all promise results 160 | are returned. For ease of use, in scalar context the first promise result is 161 | returned and the remainder are discarded. 162 | 163 | If the value passed to await is not a promise (defined as having a C 164 | method), it will be wrapped in a Mojo::Promise for consistency. This is mostly 165 | inconsequential to the user. 166 | 167 | Note that await can only take one promise as an argument. If you wanted to 168 | await multiple promises you probably want L or less likely 169 | L. 170 | 171 | my $results = await Mojo::Promise->all(@promises); 172 | 173 | =head1 AUTHORS 174 | 175 | Joel Berger 176 | 177 | Marcus Ramberg 178 | 179 | =head1 CONTRIBUTORS 180 | 181 | Sebastian Riedel 182 | 183 | =head1 ADDITIONAL THANKS 184 | 185 | Matt S Trout (mst) 186 | 187 | Paul Evans (LeoNerd) 188 | 189 | John Susek 190 | 191 | =head1 COPYRIGHT AND LICENSE 192 | 193 | Copyright (C) 2018, L and L. 194 | 195 | This program is free software, you can redistribute it and/or modify it under 196 | the terms of the Artistic License version 2.0. 197 | 198 | =head1 SEE ALSO 199 | 200 | L 201 | 202 | L 203 | 204 | L 205 | 206 | L 207 | 208 | L 209 | 210 | L 211 | 212 | L 213 | 214 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Mojolicious', '7.66'; # good promises 2 | requires 'Coro'; 3 | requires 'Sub::Util', '1.41'; # set_subname 4 | requires 'Import::Into'; 5 | 6 | test_requires 'Test::Lib'; 7 | 8 | -------------------------------------------------------------------------------- /lib/Mojo/AsyncAwait.pm: -------------------------------------------------------------------------------- 1 | package Mojo::AsyncAwait; 2 | 3 | use Carp (); 4 | use Mojo::Loader; 5 | use Import::Into; 6 | 7 | our $VERSION = '0.03'; 8 | 9 | my $backend = $ENV{MOJO_ASYNCAWAIT_BACKEND} // '+Coro'; 10 | $backend =~ s/^\+/Mojo::AsyncAwait::Backend::/; 11 | if(my $e = Mojo::Loader::load_class($backend)) { 12 | Carp::croak(ref $e ? $e : "Could not find backend $backend. Perhaps you need to install it?"); 13 | } 14 | 15 | sub import { $backend->import::into(scalar caller) } 16 | 17 | 1; 18 | 19 | 20 | =encoding utf8 21 | 22 | =head1 NAME 23 | 24 | Mojo::AsyncAwait - An Async/Await implementation for Mojolicious 25 | 26 | =head1 SYNOPSIS 27 | 28 | use Mojolicious::Lite -signatures; 29 | use Mojo::AsyncAwait; 30 | 31 | get '/' => async sub ($c) { 32 | 33 | my $mojo = await $c->ua->get_p('https://mojolicious.org'); 34 | my $cpan = await $c->ua->get_p('https://metacpan.org'); 35 | 36 | $c->render(json => { 37 | mojo => $mojo->result->code, 38 | cpan => $cpan->result->code 39 | }); 40 | }; 41 | 42 | app->start; 43 | 44 | =head1 DESCRIPTION 45 | 46 | Async/await is a language-independent pattern that allows nonblocking 47 | asynchronous code to be structured simliarly to blocking code. This is done by 48 | allowing execution to be suspended by the await keyword and returning once the 49 | promise passed to await has been fulfilled. 50 | 51 | This pattern simplies the use of both promises and nonblocking code in general 52 | and is therefore a very exciting development for writing asynchronous systems. 53 | 54 | If you are going to use this module to create async controllers actions in 55 | L applications (as seen in the L) before Mojolicious 56 | version 8.28, you are highly encouraged to also use 57 | L in order to properly handle exceptions 58 | in your action. 59 | 60 | =head1 GOALS 61 | 62 | The primary goal of this module is to provide a useful Async/Await 63 | implementation for users of the Mojolicious ecosystem. It is for this reason 64 | that L is used when new promises are created. Because this is 65 | the primary goal, the intention is for it to remain useful even as other goals 66 | are considered. 67 | 68 | Secondarily, it is intended to be a testbed for early implementations of 69 | Async/Await in the Perl 5 language. It is for this reason that the 70 | implementation details are intended to be replaceable. The result should 71 | hopefully still be backwards compatible, mostly because the interface is so 72 | simple. After all, it is just two keywords. 73 | 74 | Of course, I always intend as much as possible that Mojolicious-focused code is 75 | as useful as practically possible for the broader Perl 5 ecosystem. It is for 76 | this reason that while this module returns Ls, it can accept any 77 | then-able (read: promise) which conforms enough to the Promises/A+ standard. 78 | The Promises/A+ standard is intended to increase the interoperability of 79 | promises, and while that line becomes more gray in Perl 5 where we don't have a 80 | single ioloop implementation, we try our best. 81 | 82 | Finally the third goal is to improve the mobility of the knowledge of this 83 | pattern between languages. Users of Javascript probably are already familiar 84 | with this patthern; when coming to Perl 5 they will want to continue to use it. 85 | Likewise, as Perl 5 users take on new languages, if they are familiar with 86 | common patterns in their new language, they will have an easier time learning. 87 | Having a useable Async/Await library in Perl 5 is key to keeping Perl 5 88 | relevent in moderning coding. 89 | 90 | =head1 BACKENDS 91 | 92 | This module actually does very little on its own, it simply loads and imports 93 | backend implementations of Async/Await. The reason to use this module really 94 | would be to use current default implementation without regards to what that 95 | implementation is nor how it works. 96 | 97 | When it is loaded, the C is checked, if not set then 98 | the current default is used. 99 | 100 | # From environment 101 | BEGIN{ $ENV{MOJO_ASYNCAWAIT_BACKEND} = '+CoolBackend' } 102 | use Mojo::AsyncAwait; 103 | 104 | # Currently provided default 105 | use Mojo::AsyncAwait; 106 | 107 | The backend is specified either as a fully qualified module name, e.g. 108 | C or using the C<+> as a shortcut for 109 | C, e.g. C<+CoolBackend> which would mean exactly 110 | the same as the former. 111 | 112 | =head1 CAVEATS 113 | 114 | First and foremost, this is all a little bit crazy. Please consider carefully 115 | before using this code in production. 116 | 117 | While many languages have async/await as a core language feature, currently in 118 | Perl we must rely on modules that provide the mechanism of suspending and 119 | resuming execution. 120 | 121 | The default implementation relies on L which does some very magical 122 | things to the Perl interpreter. Other less magical implementations are in the 123 | works however none are available yet. As available implementations change or 124 | stabilize, that default may be changed. Backend implementations may be added or 125 | even be spun off. If your application depends on the backend implementation, 126 | you may import it manually or use the described mechanisms to load it. In that 127 | case you should be sure to add the backend to your dependency list in case it 128 | is spun off in the future. 129 | 130 | Also note that while a L-based implementation need not rely on L 131 | being called directly from an L function, it is currently prohibitied 132 | because it is likely that other/future implementations will rely on that 133 | behavior and thus it should not be relied upon. 134 | 135 | =head1 KEYWORDS 136 | 137 | Regardless of backend, L provides two keywords (i.e. 138 | functions), both exported by default. Depending on backend, their exact 139 | behavior might change slightly, however, implementers should attempt to follow 140 | the api described here as closely as possible. 141 | 142 | Some backends may allow additional options to be passed to the keywords; those 143 | options should be kept minimal and if possible follow the conventions described 144 | in L. This generic document will not describe 145 | those additional options. 146 | 147 | =head2 async 148 | 149 | my $sub = async sub { ... }; 150 | 151 | The async keyword wraps a subroutine as an asynchronous subroutine which is 152 | able to be suspended via L. The return value(s) of the subroutine, when 153 | called, will be wrapped in a L. 154 | 155 | The async keyword must be called with a subroutine reference, which will be the 156 | body of the async subroutine. 157 | 158 | Note that the returned subroutine reference is not invoked for you. 159 | If you want to immediately invoke it, you need to so manually. 160 | 161 | my $promise = async(sub{ ... })->(); 162 | 163 | If called with a preceding name, the subroutine will be installed into the 164 | current package with that name. 165 | 166 | async installed_sub => sub { ... }; 167 | installed_sub(); 168 | 169 | Unlike the case of an anonymous wrapped async subroutine reference described 170 | above, if the subroutine is installed, nothing is returned. 171 | 172 | =head2 await 173 | 174 | my $tx = await Mojo::UserAgent->new->get_p('https://mojolicious.org'); 175 | my @results = await (async sub { ...; return @async_results })->(); 176 | 177 | The await keyword suspends execution of an async sub until a promise is 178 | fulfilled, returning the promise's results. In list context all promise results 179 | are returned. For ease of use, in scalar context the first promise result is 180 | returned and the remainder are discarded. 181 | 182 | If the value passed to await is not a promise (defined as having a C 183 | method), it will be wrapped in a Mojo::Promise for consistency. This is mostly 184 | inconsequential to the user. 185 | 186 | Note that await can only take one promise as an argument. If you wanted to 187 | await multiple promises you probably want L or less likely 188 | L. 189 | 190 | my $results = await Mojo::Promise->all(@promises); 191 | 192 | =head1 AUTHORS 193 | 194 | Joel Berger 195 | 196 | Marcus Ramberg 197 | 198 | =head1 CONTRIBUTORS 199 | 200 | Sebastian Riedel 201 | 202 | =head1 ADDITIONAL THANKS 203 | 204 | Matt S Trout (mst) 205 | 206 | Paul Evans (LeoNerd) 207 | 208 | John Susek 209 | 210 | =head1 COPYRIGHT AND LICENSE 211 | 212 | Copyright (C) 2018, L and L. 213 | 214 | This program is free software, you can redistribute it and/or modify it under 215 | the terms of the Artistic License version 2.0. 216 | 217 | =head1 SEE ALSO 218 | 219 | L 220 | 221 | L 222 | 223 | L 224 | 225 | L 226 | 227 | L 228 | 229 | L 230 | 231 | L 232 | 233 | =cut 234 | 235 | -------------------------------------------------------------------------------- /lib/Mojo/AsyncAwait/Backend/Coro.pm: -------------------------------------------------------------------------------- 1 | package Mojo::AsyncAwait::Backend::Coro; 2 | use Mojo::Base -strict; 3 | 4 | use Carp (); 5 | use Coro::State (); 6 | use Mojo::Util; 7 | use Mojo::Promise; 8 | use Sub::Util (); 9 | 10 | use Exporter 'import'; 11 | 12 | our @EXPORT = (qw/async await/); 13 | 14 | my $main = Coro::State->new; 15 | $main->{desc} = 'Mojo::AsyncAwait::Backend::Coro/$main'; 16 | 17 | # LIFO stack of coroutines waiting to come back to 18 | # always has $main as the bottom of the stack 19 | my @stack = ($main); 20 | 21 | # Coroutines that are ostensible done but need someone to kill them 22 | my @clean; 23 | 24 | # _push adds a coroutine to the stack and enters it 25 | # when control returns to the original pusher, it will clean up 26 | # any coroutines that are waiting to be cleaned up 27 | 28 | sub _push { 29 | push @stack, @_; 30 | $stack[-2]->transfer($stack[-1]); 31 | $_->cancel for @clean; 32 | @clean = (); 33 | } 34 | 35 | # _pop pops the current coroutine off the stack. If given a callback, it calls 36 | # a callback on it, otherwise, schedules it for cleanup. It then transfers to 37 | # the next one on the stack. Note that it can't pop-and-return (which would 38 | # make more sense) because any action on it must happen before control is 39 | # transfered away from it 40 | 41 | sub _pop (;&) { 42 | Carp::croak "Cannot leave the main thread" 43 | if $stack[-1] == $main; 44 | my ($cb) = @_; 45 | my $current = pop @stack; 46 | if ($cb) { $cb->($current) } 47 | else { push @clean, $current } 48 | $current->transfer($stack[-1]); 49 | } 50 | 51 | sub async { 52 | my $body = pop; 53 | my $opts = _parse_opts(@_); 54 | my @caller = caller; 55 | 56 | my $subname = "$caller[0]::__ASYNCSUB__"; 57 | my $bodyname = "$caller[0]::__ASYNCBODY__"; 58 | if (defined(my $name = $opts->{-name})) { 59 | $subname = $opts->{-install} ? "$caller[0]::$name" : "$subname($name)"; 60 | $bodyname .= "($name)"; 61 | } 62 | my $desc = "declared at $caller[1] line $caller[2]"; 63 | 64 | Sub::Util::set_subname($bodyname => $body) 65 | if Sub::Util::subname($body) =~ /::__ANON__$/; 66 | 67 | my $wrapped = sub { 68 | my @caller = caller; 69 | my $promise = Mojo::Promise->new; 70 | my $coro = Coro::State->new(sub { 71 | eval { 72 | BEGIN { $^H{'Mojo::AsyncAwait::Backend::Coro/async'} = 1 } 73 | $promise->resolve($body->(@_)); 1 74 | } or $promise->reject($@); 75 | _pop; 76 | }, @_); 77 | $coro->{desc} = "$subname called at $caller[1] line $caller[2], $desc"; 78 | _push $coro; 79 | return $promise; 80 | }; 81 | 82 | if ($opts->{-install}) { 83 | Mojo::Util::monkey_patch $caller[0], $opts->{-name} => $wrapped; 84 | return; 85 | } 86 | 87 | Sub::Util::set_subname $subname => $wrapped; 88 | return $wrapped; 89 | } 90 | 91 | # this prototype prevents the perl tokenizer from seeing await as an 92 | # indirect method 93 | 94 | sub await (*) { 95 | { 96 | # check that our caller is actually an async function 97 | no warnings 'uninitialized'; 98 | my $level = 1; 99 | my ($caller, $hints) = (caller($level))[3, 10]; 100 | 101 | # being inside of an eval is ok too 102 | ($caller, $hints) = (caller(++$level))[3, 10] while $caller eq '(eval)'; 103 | 104 | Carp::croak 'await may only be called from in async function' 105 | unless $hints->{'Mojo::AsyncAwait::Backend::Coro/async'}; 106 | } 107 | 108 | my $promise = Mojo::Promise->resolve($_[0]); 109 | 110 | my (@retvals, $err); 111 | _pop { 112 | my $current = shift; 113 | $promise->then( 114 | sub { 115 | @retvals = @_; 116 | _push $current; 117 | }, 118 | sub { 119 | $err = shift; 120 | _push $current; 121 | } 122 | ); 123 | }; 124 | 125 | # "_push $current" in the above callback brings us here 126 | Carp::croak($err) if $err; 127 | return wantarray ? @retvals : $retvals[0]; 128 | } 129 | 130 | sub _parse_opts { 131 | return {} unless @_; 132 | return { 133 | -name => shift, 134 | -install => 1, 135 | } if @_ == 1; 136 | 137 | my %opts = @_; 138 | Carp::croak 'Cannot install a sub without a name' 139 | if $opts{-install} && !defined $opts{-name}; 140 | 141 | return \%opts; 142 | } 143 | 144 | 1; 145 | 146 | =encoding utf8 147 | 148 | =head1 NAME 149 | 150 | Mojo::AsyncAwait::Backend::Coro - An Async/Await implementation for Mojolicious using Coro 151 | 152 | =head1 SYNOPSIS 153 | 154 | use Mojolicious::Lite -signatures; 155 | use Mojo::AsyncAwait; 156 | 157 | get '/' => async sub ($c) { 158 | 159 | my $mojo = await $c->ua->get_p('https://mojolicious.org'); 160 | my $cpan = await $c->ua->get_p('https://metacpan.org'); 161 | 162 | $c->render(json => { 163 | mojo => $mojo->result->code, 164 | cpan => $cpan->result->code 165 | }); 166 | }; 167 | 168 | app->start; 169 | 170 | =head1 DESCRIPTION 171 | 172 | As the name suggests, L is an implementation 173 | of the Async/Await pattern, using L and L. See more at 174 | L. 175 | 176 | =head1 CAVEATS 177 | 178 | This implementation relies on L which does some very magical things to 179 | the Perl interpreter. All caveats that apply to using L apply to 180 | this module as well. 181 | 182 | Also note that while a L-based implementation need not rely on L 183 | being called directly from an L function, it is currently prohibitied 184 | because it is likely that other/future implementations will rely on that 185 | behavior and thus it should not be relied upon. 186 | 187 | =head1 KEYWORDS 188 | 189 | L provides two keywords (i.e. functions), both 190 | exported by default. They are re-exported by L if it is the 191 | chosen implementation. 192 | 193 | =head2 async 194 | 195 | my $sub = async sub { ... }; 196 | 197 | The async keyword wraps a subroutine as an asynchronous subroutine which is 198 | able to be suspended via L. The return value(s) of the subroutine, when 199 | called, will be wrapped in a L. 200 | 201 | The async keyword must be called with a subroutine reference, which will be the 202 | body of the async subroutine. 203 | 204 | Note that the returned subroutine reference is not invoked for you. 205 | If you want to immediately invoke it, you need to so manually. 206 | 207 | my $promise = async(sub{ ... })->(); 208 | 209 | If called with a preceding name, the subroutine will be installed into the current package with that name. 210 | 211 | async installed_sub => sub { ... }; 212 | installed_sub(); 213 | 214 | If called with key-value arguments starting with a dash, the following options are available. 215 | 216 | =over 217 | 218 | =item -install 219 | 220 | If set to a true value, the subroutine will be installed into the current package. 221 | Default is false. 222 | Setting this value to true without a C<-name> is an error. 223 | 224 | =item -name 225 | 226 | If C<-install> is false, this is a diagnostic name to be included in the subname for debugging purposes. 227 | This name is seen in diagnostic information, like stack traces. 228 | 229 | my $named_sub = async -name => my_name => sub { ... }; 230 | $named_sub->(); 231 | 232 | Otherwise this is the name that will be installed into the current package. 233 | 234 | =back 235 | 236 | Therefore, passing a bare name as is identical to setting both C<-name> and C<< -install => 1 >>. 237 | 238 | async -name => installed_sub, -install => 1 => sub { ... }; 239 | installed_sub(); 240 | 241 | If the subroutine is installed, whether by passing a bare name or the C<-install> option, nothing is returned. 242 | Otherwise the return value is the wrapped async subroutine reference. 243 | 244 | =head2 await 245 | 246 | my $tx = await Mojo::UserAgent->new->get_p('https://mojolicious.org'); 247 | my @results = await (async sub { ...; return @async_results })->(); 248 | 249 | The await keyword suspends execution of an async sub until a promise is 250 | fulfilled, returning the promise's results. In list context all promise results 251 | are returned. For ease of use, in scalar context the first promise result is 252 | returned and the remainder are discarded. 253 | 254 | If the value passed to await is not a promise (defined as having a C 255 | method), it will be wrapped in a Mojo::Promise for consistency. This is mostly 256 | inconsequential to the user. 257 | 258 | Note that await can only take one promise as an argument. If you wanted to 259 | await multiple promises you probably want L or less likely 260 | L. 261 | 262 | my $results = await Mojo::Promise->all(@promises); 263 | 264 | =head1 SEE ALSO 265 | 266 | L, L, L, L 267 | 268 | =cut 269 | -------------------------------------------------------------------------------- /metamerge.json: -------------------------------------------------------------------------------- 1 | { 2 | "license" : [ "perl_5" ], 3 | "meta-spec" : { 4 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 5 | "version" : "2" 6 | }, 7 | "resources" : { 8 | "repository" : { 9 | "url" : "http://github.com/mojolicious/Mojo-AsyncAwait" 10 | }, 11 | "bugtracker" : { 12 | "web" : "http://github.com/mojolicious/Mojo-AsyncAwait/issues" 13 | }, 14 | "license" : [ "http://dev.perl.org/licenses/" ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /t/app.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Test::Mojo; 3 | 4 | use Mojo::AsyncAwait; 5 | use Mojolicious::Lite; 6 | 7 | my @hooks; 8 | 9 | app->hook( 10 | after_build_tx => sub { 11 | my ($tx, $app) = @_; 12 | push @hooks, $tx; 13 | } 14 | ); 15 | 16 | app->hook( 17 | around_dispatch => sub { 18 | my ($next, $c) = @_; 19 | push @hooks, $c; 20 | $next->(); 21 | push @hooks, 'after_dispatch'; 22 | } 23 | ); 24 | 25 | app->hook( 26 | around_action => sub { 27 | my ($next, $c) = @_; 28 | push @hooks, 'before_action'; 29 | my $res = $next->(); 30 | $res->then(sub{ push @hooks, shift }); 31 | } 32 | ); 33 | 34 | get '/' => async sub { 35 | my $c = shift; 36 | $c->render_later; 37 | my $promise = Mojo::Promise->new; 38 | Mojo::IOLoop->timer(1 => sub { $promise->resolve("hello world") }); 39 | my $text = await $promise; 40 | $c->render(text => $text); 41 | return "action done"; 42 | }; 43 | 44 | my $t = Test::Mojo->new; 45 | $t->get_ok('/')->status_is(200)->content_is("hello world"); 46 | 47 | #warn Data::Dumper::Dumper(\@hooks); 48 | isa_ok($hooks[0], 'Mojo::Transaction'); 49 | isa_ok($hooks[1], 'Mojolicious::Controller'); 50 | is($hooks[2], 'before_action'); 51 | is($hooks[3], 'after_dispatch'); 52 | is($hooks[4], 'action done'); 53 | 54 | done_testing; 55 | -------------------------------------------------------------------------------- /t/app_all.t: -------------------------------------------------------------------------------- 1 | use Mojolicious::Lite; 2 | use Mojo::Promise; 3 | use Mojo::AsyncAwait; 4 | 5 | use Test::More; 6 | use Test::Mojo; 7 | 8 | # N.B. this test tests the case from 9 | # https://mojolicious.org/perldoc/Mojolicious/Guides/Cookbook#Synchronizing-non-blocking-operations 10 | # substituting $external in place of calling out to metacpan 11 | 12 | my $external = Mojolicious->new; 13 | app->ua->server->app($external); 14 | $external->routes->get('/x' => {text => 'X'}); 15 | $external->routes->get('/y' => {text => 'Y'}); 16 | 17 | get '/' => async sub { 18 | my $c = shift; 19 | 20 | my $x = $c->ua->get_p('/x'); 21 | my $y = $c->ua->get_p('/y'); 22 | 23 | # Render a response once both promises have been resolved 24 | my ($got_x, $got_y) = await Mojo::Promise->all($x, $y); 25 | 26 | $c->render(json => { 27 | x => $got_x->[0]->result->text, 28 | y => $got_y->[0]->result->text, 29 | }); 30 | }; 31 | 32 | my $t = Test::Mojo->new; 33 | $t->get_ok('/') 34 | ->status_is(200) 35 | ->json_is({x => 'X', y => 'Y'}); 36 | 37 | done_testing; 38 | 39 | -------------------------------------------------------------------------------- /t/await_async.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | 5 | use Mojo::IOLoop; 6 | use Mojo::AsyncAwait; 7 | 8 | use Test::Lib; 9 | use TestHelper; 10 | 11 | sub double { 12 | my $in = shift; 13 | my $p = Mojo::Promise->new; 14 | Mojo::IOLoop->timer(0.2 => sub { $p->resolve(2 * $in) }); 15 | return $p; 16 | } 17 | 18 | async quad => sub { 19 | my $val = await double(shift); 20 | my $ret = await double($val); 21 | return $ret; 22 | }; 23 | 24 | my $ticker = ticker(); 25 | 26 | my $answer; 27 | async(sub { $answer = await quad(3) })->()->wait; 28 | 29 | is $answer, 12, 'got expected answer'; 30 | ok $ticker->() > 2, 'got multiple ticks'; 31 | 32 | done_testing; 33 | 34 | 35 | -------------------------------------------------------------------------------- /t/basic.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | 5 | use Mojo::IOLoop; 6 | use Mojo::AsyncAwait; 7 | 8 | use Test::Lib; 9 | use TestHelper; 10 | 11 | sub double { 12 | my $in = shift; 13 | my $p = Mojo::Promise->new; 14 | Mojo::IOLoop->timer(0.5 => sub { $p->resolve(2 * $in) }); 15 | return $p; 16 | } 17 | 18 | my $ticker = ticker(); 19 | 20 | my $answer; 21 | async(sub { $answer = await double(21) })->()->wait; 22 | 23 | is $answer, 42, 'got expected answer'; 24 | ok $ticker->() > 2, 'got multiple ticks'; 25 | 26 | done_testing; 27 | 28 | -------------------------------------------------------------------------------- /t/exceptions.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | 5 | use Mojo::AsyncAwait; 6 | use Mojo::IOLoop; 7 | use Mojo::Promise; 8 | 9 | eval { await 42 }; 10 | like $@, qr'await may only be called from in async function'; 11 | 12 | sub answer { 13 | my $p = Mojo::Promise->new; 14 | Mojo::IOLoop->next_tick(sub{ $p->resolve(42) }); 15 | return $p; 16 | }; 17 | 18 | subtest 'eval' => sub { 19 | my $answer; 20 | async(sub { 21 | eval { $answer = await answer() }; 22 | })->()->wait; 23 | 24 | is $answer, 42, 'got answer'; 25 | }; 26 | 27 | subtest 'deep call (good)' => sub { 28 | my $calls_answer = sub { 29 | return answer(); 30 | }; 31 | 32 | my ($answer, $err); 33 | my $async = async sub { 34 | $answer = await $calls_answer->(); 35 | }; 36 | $async->()->catch(sub{ $err = shift })->wait; 37 | 38 | is $answer, 42, 'got expected answer'; 39 | ok !defined $err, 'this is the correct usage'; 40 | }; 41 | 42 | subtest 'deep call (bad)' => sub { 43 | my $calls_answer = sub { 44 | return await answer(); 45 | }; 46 | 47 | my ($answer, $err); 48 | my $async = async sub { 49 | $answer = $calls_answer->(); 50 | }; 51 | $async->()->catch(sub{ $err = shift })->wait; 52 | 53 | ok !defined $answer, 'should not get answer because of deep await call'; 54 | like $err, qr'await may only be called from in async function', 'got expected error'; 55 | }; 56 | 57 | 58 | done_testing; 59 | 60 | -------------------------------------------------------------------------------- /t/installed.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | 5 | use Mojo::IOLoop; 6 | use Mojo::AsyncAwait; 7 | use Sub::Util 'subname'; 8 | 9 | use Test::Lib; 10 | use TestHelper; 11 | 12 | sub answer { 13 | my $p = Mojo::Promise->new; 14 | Mojo::IOLoop->timer(0.5 => sub { $p->resolve(42) }); 15 | return $p; 16 | } 17 | 18 | my $ticker = ticker(); 19 | 20 | my $answer; 21 | my $body = sub { $answer = await answer() }; 22 | async doit => $body; 23 | 24 | my $package = __PACKAGE__; 25 | is subname($body), "${package}::__ASYNCBODY__(doit)", 'correct body name'; 26 | is subname(\&doit), "${package}::doit", 'correct sub name'; 27 | 28 | doit()->wait; 29 | 30 | is $answer, 42, 'got expected answer'; 31 | ok $ticker->() > 2, 'got multiple ticks'; 32 | 33 | done_testing; 34 | 35 | 36 | -------------------------------------------------------------------------------- /t/lib/TestHelper.pm: -------------------------------------------------------------------------------- 1 | package TestHelper; 2 | 3 | use Mojo::Base -strict; 4 | 5 | use Mojo::IOLoop; 6 | use Test::More (); 7 | 8 | use Exporter 'import'; 9 | 10 | our @EXPORT = (qw/ticker/); 11 | 12 | sub ticker { 13 | my ($tick, $timeout) = @_; 14 | 15 | my $count = 0; 16 | 17 | my ($ticker, $timer); 18 | $ticker = Mojo::IOLoop->recurring(($tick // 0.1) => sub { $count++ }); 19 | 20 | $timer = Mojo::IOLoop->timer( 21 | ($timeout // 5) => sub { 22 | Test::More::fail 'timeout'; 23 | $timer = undef; 24 | Mojo::IOLoop->stop; 25 | } 26 | ); 27 | 28 | return sub { 29 | defined($_) && Mojo::IOLoop->remove($_) for ($ticker, $timer); 30 | return $count; 31 | }; 32 | } 33 | 34 | 1; 35 | 36 | -------------------------------------------------------------------------------- /t/named.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | 5 | use Mojo::IOLoop; 6 | use Mojo::AsyncAwait; 7 | use Sub::Util 'subname'; 8 | 9 | use Test::Lib; 10 | use TestHelper; 11 | 12 | sub answer { 13 | my $p = Mojo::Promise->new; 14 | Mojo::IOLoop->timer(0.5 => sub { $p->resolve(42) }); 15 | return $p; 16 | } 17 | 18 | my $ticker = ticker(); 19 | 20 | my $answer; 21 | my $body = sub { $answer = await answer() }; 22 | my $doit = async -name => doit => $body; 23 | 24 | my $package = __PACKAGE__; 25 | is subname($body), "${package}::__ASYNCBODY__(doit)", 'correct body name'; 26 | is subname($doit), "${package}::__ASYNCSUB__(doit)", 'correct sub name'; 27 | 28 | $doit->()->wait; 29 | 30 | is $answer, 42, 'got expected answer'; 31 | ok $ticker->() > 2, 'got multiple ticks'; 32 | 33 | done_testing; 34 | 35 | 36 | -------------------------------------------------------------------------------- /t/promise_actions.t: -------------------------------------------------------------------------------- 1 | use Mojolicious::Lite; 2 | 3 | use Scalar::Util 'blessed'; 4 | use Mojo::AsyncAwait; 5 | 6 | use Test::More; 7 | use Test::Mojo; 8 | 9 | if (eval{ Mojolicious->VERSION(8.28); 1}) { 10 | plan skip_all => 'Mojolicious 8.28+ handles PromiseActions in core'; 11 | } 12 | 13 | # specifically use development mode for the exception page 14 | app->mode('development'); 15 | 16 | # manually install the guts of Mojolicious::Plugin::PromiseActions 17 | # this tests that exception handling at the main coro can be accomplished from the hook 18 | hook around_action => sub { 19 | my ($next, $c) = @_; 20 | my $res = $next->(); 21 | if (blessed($res) && $res->can('then')) { 22 | my $tx = $c->render_later; 23 | $res->then(undef, sub { $c->reply->exception('XXX:' . pop) and undef $tx })->wait; 24 | } 25 | return $res; 26 | }; 27 | 28 | get '/' => async sub { die "Argh\n" }; 29 | 30 | my $t = Test::Mojo->new; 31 | 32 | $t->get_ok('/') 33 | ->status_is(500) 34 | ->text_is('#error' => "XXX:Argh\n"); 35 | 36 | done_testing; 37 | 38 | -------------------------------------------------------------------------------- /t/scalar.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | 5 | use Mojo::IOLoop; 6 | use Mojo::AsyncAwait; 7 | 8 | use Test::Lib; 9 | use TestHelper; 10 | 11 | sub double { 12 | my $in = shift; 13 | return 2 * $in; 14 | } 15 | 16 | my $ticker = ticker(0); 17 | 18 | my $answer; 19 | async(sub { $answer = await double(21) })->()->wait; 20 | 21 | is $answer, 42, 'got expected answer'; 22 | ok $ticker->(), 'got at least one tick'; 23 | 24 | done_testing; 25 | 26 | --------------------------------------------------------------------------------