view release on metacpan or search on metacpan
devdata/https_mojolicious.io_blog_2018_12_03_higher-order-promises_ view on Meta::CPAN
<pre><code>use Mojo::Promise;
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
my @urls = ( ... );
my @all_sites = map { $ua->get_p( $_ ) } @urls;
my $all_promise = Mojo::Promise
->all( @all_sites )
->then(
sub { say "They all worked!" },
sub { say "One of them didn't work!" }
);
</code></pre>
<p>The Promises aren't required to do their work in any order, though, so don't base your work on that.</p>
<h3>First come, first served</h3>
<p>A "race" resolves when the first Promise is no longer pending and after that doesn't need the other Promises to keep working.</p>
<pre><code>use Mojo::Promise;
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
my @urls = ( ... );
my @all_sites = map { $ua->get_p( $_ ) } @urls;
my $all_promise = Mojo::Promise
->race( @all_sites )
->then(
sub { say "One of them finished!" },
);
</code></pre>
<h3>Any</h3>
<p>An "any" Promise resolves immediately when the first of its Promises resolves. This is slightly different from <code>race</code> because at least one Promise must resolve. A Promise being rejected doesn't resolve the <code>any</code>...
<p>Here's a program that extracts the configured CPAN mirrors and tests that it can get the <em>index.html</em> file. To ensure that it finds that file and not some captive portal, it looks for "Jarkko" in the body:</p>
<pre><code>use v5.28;
devdata/https_mojolicious.io_blog_2018_12_03_higher-order-promises_ view on Meta::CPAN
use Mojo::Promise;
use Mojo::Promise::Role::HigherOrder;
use Mojo::UserAgent;
use Mojo::URL;
use lib catfile( $ENV{HOME}, '.cpan' );
my @mirrors = eval {
no warnings qw(once);
my $file = Mojo::URL->new( 'index.html' );
require CPAN::MyConfig;
map { say "1: $_"; $file->clone->base(Mojo::URL->new($_))->to_abs }
$CPAN::Config->{urllist}->@*
};
die "Did not find CPAN::MyConfig\n" unless @mirrors;
my $ua = Mojo::UserAgent->new;
my @all_sites = map {
$ua->get_p( $_ )->then( sub ($tx) {
die unless $tx->result->body =~ /Jarkko/ });
} @mirrors;
my $any_promise = Mojo::Promise
->with_roles( '+Any' )
->any( @all_sites )
->then(
sub { say "At least one of them worked!" },
sub { say "None of them worked!" },
);
$any_promise->wait;
</code></pre>
<h3>Some</h3>
<p>A <code>some</code> Promise resolves when a certain number of its Promises resolve. You specify how many you need to succeed and the the <code>some</code> Promise resolves when that number resolve. This should act like <code>some</code> in <a href...
<p>This example modifies the previous program to find more than one mirror that works. You can specify the number that need to work for the <code>some</code> to resolve:</p>
devdata/https_mojolicious.io_blog_2018_12_03_higher-order-promises_ view on Meta::CPAN
use Mojo::Promise;
use Mojo::Promise::Role::HigherOrder;
use Mojo::UserAgent;
use Mojo::URL;
use lib catfile( $ENV{HOME}, '.cpan' );
my @mirrors = eval {
no warnings qw(once);
my $file = Mojo::URL->new( 'index.html' );
require CPAN::MyConfig;
map { say "1: $_"; $file->clone->base(Mojo::URL->new($_))->to_abs }
$CPAN::Config->{urllist}->@*
};
die "Did not find CPAN::MyConfig\n" unless @mirrors;
my $ua = Mojo::UserAgent->new;
my $count = 2;
my @all_sites = map {
$ua->get_p( $_ )->then( sub ($tx) {
die unless $tx->result->body =~ /Jarkko/ });
} @mirrors;
my $some_promise = Mojo::Promise
->with_roles( '+Some' )
->some( \@all_sites, 2 )
->then(
sub { say "At least $count of them worked!" },
sub { say "None of them worked!" },
);
$some_promise->wait;
</code></pre>
<h3>None</h3>
<p>A "none" Promise resolves when all of the its Promises are rejected. It's a trivial case that might be useful somewhere and I created it mostly because Perl 6 has a <a href="https://docs.perl6.org/routine/none">none Junction</a> (whi...
<p>For this very simple example, consider the task to check that no sites are that annoying "404 File Not Found":</p>
devdata/https_mojolicious.io_blog_2018_12_03_higher-order-promises_ view on Meta::CPAN
my $p = $ua->get_p( $_ );
$p->then( sub ( $tx ) {
$tx->res->code == 404 ? $tx->req->url : die $tx->req->url
} );
} @urls;
my $all_promise = Mojo::Promise
->with_roles( '+None' )
->none( @all_sites )
->then(
sub { say "None of them were 404!" },
sub { say "At least one was 404: @_!" },
);
$all_promise->wait;
</code></pre>
<h2>Conclusion</h2>
<p>It's easy to make new Promises out of smaller ones to represent complex situations. You can combine the Promises that Mojolicious creates for you with your own handmade Promises to do almost anything you like.</p>
</section>
devdata/https_mojolicious.io_blog_2018_12_05_compound-selectors_ view on Meta::CPAN
</div>
<div class="post-thumb">
<!-- theme suggests 1300x500 -->
<img alt="Sled dogs waiting to run" src="/blog/2018/12/05/compound-selectors/banner.jpg">
</div>
<div class="post-content">
<section id="section-1">
<p>When people tell me that I can't (they mean shouldn't) parse HTML with a regex, I say "hold my beer". It isn't a matter of skill or attitude so much as convenience. Doing it the right way was not always so e...
</section>
<section id="section-2">
<p>The trick was always to isolate the interesting HTML. I could do that excising all of the data around the interesting parts:</p>
<pre><code>my $html = ...;
$html =~ s/.*?<table class="foo".*?>//;
$html =~ s/<\/table>.*//;
</code></pre>
devdata/https_mojolicious.io_blog_2018_12_05_compound-selectors_ view on Meta::CPAN
<img id="farnworth " class="human" src="..." />
<ul class="employees">
<li><img id="bender" class="robot" src="..." /></li>
<li><img id="fry" class="human" src="..." /></li>
<li><img id="leela" class="mutant" src="..." /></li>
</ul>
HTML
my $dom = Mojo::DOM->new( $html );
say $dom->find( $selector )->join( "\n" );
</code></pre>
<p>Run this with no argument and I see all the <code>img</code> tags:</p>
<pre><code>$ perl html.pl
<img class="human" id="farnworth " src="...">
<img class="robot" id="bender" src="...">
<img class="human" id="fry" src="...">
<img class="mutant" id="leela" src="...">
</code></pre>
devdata/https_mojolicious.io_blog_2018_12_05_compound-selectors_ view on Meta::CPAN
<img class="human" id="farnworth " src="...">
<img class="human" id="fry" src="...">
</code></pre>
<p>But what if I wanted just the human images in the list? I have to work a little bit harder. I specify a compound selector that notes that the <code>img</code> has to be in an <code>li</code> tag:</p>
<pre><code>$ perl html.pl "li img.human"
<img class="human" id="fry" src="...">
</code></pre>
<p>Imagine, then, more complicated HTML with other lists that also had images? I could add another selector to say it has to be in a certain sort of <code>ul</code> tag:</p>
<pre><code>$ perl html.pl "ul.employees li img.human"
<img class="human" id="fry" src="...">
</code></pre>
<p>If nothing should be between those tags. I can connect the selector with <code>></code> to mean those should be immediate children instead of descendants:</p>
<pre><code>$ perl html.pl "ul.employees > li > img.human"
<img class="human" id="fry" src="...">
</code></pre>
devdata/https_mojolicious.io_blog_2018_12_05_compound-selectors_ view on Meta::CPAN
use charnames qw();
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
my $url = 'https://blog.emojipedia.org/new-unicode-9-emojis/';
my $tx = $ua->get( $url );
die "That didn't work!\n" if $tx->error;
say $tx->result
->dom
->find( 'ul:not( [class] ) li a' )
->map( 'text' )
->map( sub {
my $c = substr $_, 0, 1;
[ $c, ord($c), charnames::viacode( ord($c) ) ]
})
->sort( sub { $a->[1] <=> $b->[1] } )
->map( sub {
sprintf '%s (U+%05X) %s', $_->@*
devdata/https_mojolicious.io_blog_2018_12_05_compound-selectors_ view on Meta::CPAN
</table>
HTML
my @scores = Mojo::DOM->new( $html )
->find( 'table.scores > tr > td:last-child' )
->map( 'text' )
->each
;
my $grand = sum( @scores );
say "Grand total: $grand";
</code></pre>
<p><em style="font-size: 10px">
Editor's note: Unfortunately this example breaks our syntax highlighter. This is the site's fault not the author. We're trying to find a better way to render it short of rewriting the rendering engine.
</em></p>
<h2>Conclusion</h2>
<p>Even for an old programmer like me, dealing with HTML through CSS Selectors applied by Mojolicious is much easier than what I was doing before (which was dirty and much easier than doing it correctly). With a little skill creating compound selecto...
devdata/https_mojolicious.io_blog_2018_12_09_add-a-theme-system-to-your-mojolicious-app_ view on Meta::CPAN
$self->renderer->paths([$self->home->rel_file('themes/default/templates')]);
$self->static->paths([$self->home->rel_file('themes/default/public')]);
</code></pre>
<h2>Add a way to use another theme</h2>
<p>As said before, Mojolicious search for static files or templates in the first directory of the registered paths, and goes to next if it can't find the files or templates.</p>
<p>Thus, we need to add our new theme paths before the default ones.</p>
<p>Let's say that we created a <code>christmas</code> theme which files are in <code>themes/christmas/public</code> and which templates are in <code>themes/christmas/templates</code>.</p>
<p>Our snippet to add to the code becomes:</p>
<pre><code># Replace the default paths
$self->renderer->paths([$self->home->rel_file('themes/default/templates')]);
$self->static->paths([$self->home->rel_file('themes/default/public')]);
# Put the new theme first
unshift @{$self->renderer->paths}, $self->home->rel_file('themes/christmas/templates');
unshift @{$self->static->paths}, $self->home->rel_file('themes/christmas/public');
</code></pre>
<p>By doing that way, we can overload the default files.</p>
<p>You don't have to modify each file of the default theme to have a new theme: just copy the files you want to overload in your new theme directory and it will be used instead of the default one.</p>
<p>Let's say that you have a <code>background.png</code> file in your default theme:</p>
<pre><code>$ cd themes/default
$ tree public templates
public
âÂÂâÂÂâ background.png
âÂÂâÂÂâ index.html
templates
âÂÂâÂÂâ example
âÂÂààâÂÂâÂÂâ welcome.html.ep
âÂÂâÂÂâ layouts
devdata/https_mojolicious.io_blog_2018_12_12_dancer-and-minion_ view on Meta::CPAN
});
</code></pre>
<p>Now, we apply the configuration (read below) to the worker. When the worker starts, it tells us information about how it was configured (this was really useful during development):</p>
<pre><code>my $max_jobs = $hostconfig->{ max_children };
my @queues = @{ $hostconfig->{ queues }};
if( $minion->has_invalid_queues( @queues ) ){
print "Invalid job queues specified: " . join( ',', $minion->get_invalid_queues( @queues ) );
say ". Aborting!";
exit 1;
}
say "Starting Job Queue Worker on " . get_hostname();
say "- Configured to run a max of $max_jobs jobs";
say "- Listening for jobs on queues: ", join(', ', @queues );
$worker->status->{ jobs } = $max_jobs;
$worker->status->{ queues } = \@queues;
$worker->run;
</code></pre>
<p>Remember the YAML file we used to configure things up above? This last bit pulls the information for the host this worker is running on (<code>get_hostname()</code> is a home-grown
hostname function):</p>
<pre><code>sub get_hostconfig {
my $minion_config = MyJob::Config->new({ filename => "environments/minions.yml" })->config;
devdata/https_mojolicious.io_blog_2018_12_13_taking-on-roles_ view on Meta::CPAN
</section>
<section id="section-2">
<p>There are times that I want <a href="https://mojolicious.org/perldoc/Mojo/File">Mojo::File</a> to act a bit differently than it does. Often I have a path where I want to combine only the basename with a different directory. I end...
<pre><code>use Mojo::File qw(path);
my $path = Mojo::File->new( '/Users/brian/bin/interesting.txt' );
my $dir = Mojo::File->new( '/usr/local/bin' );
my $new_path = $dir->child( $path->basename );
say $new_path; # /usr/local/bin/interesting.txt
</code></pre>
<p>That's annoying. I don't like that it takes so many steps. There are a few methods that I'd like instead. I'd rather be able to write it like this, where I start with the interesting file and keep working on it instead of switching...
<pre><code>use Mojo::File qw(path);
my $new_path = Mojo::File
->new( '/Users/brian/bin/interesting.txt' )
->rebase( '/usr/local/bin' ); # this isn't a method
say $new_path; # /usr/local/bin/interesting.txt
</code></pre>
<p>I could go through various Perl tricks to add this method to <code>Mojo::File</code> through <a href="https://mojolicious.org/perldoc/Mojo/Util#monkey_patch">monkey patching</a> or subclassing. But, as usual, Mojolicious anticipates my desire and ...
<p>You can read about roles on your own while I jump into it. First, I create a class to represent my role. I define the method(s) I want. I use the name of the package I want to affect, add <code>::Role::</code>, then the name I'd like to use; i...
<pre><code>package Mojo::File::Role::rebase {
use Mojo::Base qw(-role -signatures);
sub rebase ($file, $dir) {
devdata/https_mojolicious.io_blog_2018_12_13_taking-on-roles_ view on Meta::CPAN
</code></pre>
<p>I'd need to use this if I didn't follow the naming convention:</p>
<pre><code>my $file_class = Mojo::File->with_roles(
'I::Totally::Rejected::The::Convention::rebase' );
</code></pre>
<p>The <code>$file_class</code> is a string with the new class name. Behind that class there is some multiple inheritance magic that you'll be much happier ignoring. I don't need to use a bareword class name to call class methods; a string in...
<pre><code>say $file_class
->new( '/Users/brian/bin/interesting.txt' )
->rebase( '/usr/local/bin/' );
</code></pre>
<p>That's much cleaner than what I was doing before and I like how this flows. But what if I get an already-created <a href="https://mojolicious.org/perldoc/Mojo/File">Mojo::File</a> object from something else? I can apply the role <em>ad hoc</em...
<pre><code>my $file = path( '/Users/brian/bin/interesting.txt' );
say $file
->with_roles( '+rebase' )
->rebase( '/usr/local/bin/' );
</code></pre>
<p>I can go further. Any methods I add to my role become part of the class. I often want to get the digests of files and although <a href="https://mojolicious.org/perldoc/Mojo/Util">Mojo::Util</a> makes that easier with some convenience functions, I ...
<pre><code>use Mojo::File;
package Mojo::File::Role::MyUtils {
use Mojo::Base qw(-role -signatures);
devdata/https_mojolicious.io_blog_2018_12_13_taking-on-roles_ view on Meta::CPAN
sub sha1 ($file) {
sha1_sum( $file->slurp )
}
}
my $file = Mojo::File
->with_roles( '+MyUtils' )
->new(shift);
say $file->sha1;
say $file->md5;
</code></pre>
<p>You can read more about roles in Joel Berger's 2017 Mojolicious Advent Calendar entry <a href="https://mojolicious.io/blog/2017/12/13/day-13-more-about-roles/">Day 13: More About Roles</a>. Curiously that was on Day 13 too, although I don'...
</section>
<small><p><a class="external text" href="https://www.flickr.com/photos/eskimo_jo/27387510917/in/photolist-HJ99Q6-fziGbA-fypQyz-7pCEK2-e6kG1-8NxgJh-9gKfaE-eEhB2w-9wc7yk-fwnRyf-62b1R6-27u47Nh-4ohfrN-9o2vut-2xyVmv-2yas82-ctV3fq-nBYJN4-q6zk...
</small>
<p class="tags">
<span>Tagged in </span>:
devdata/https_mojolicious.io_blog_2018_12_14_a-practical-example-of-mojo-dom_ view on Meta::CPAN
$e->{Folder} = rename_files($e) and $e->{Hash} = '' if $e->{Hash};
}
# save xml file so we don't try to rename the pointclouds again
$file->spurt($dom);
}
sub rename_files {
# rename pointcloud folder and database file
my $e = shift;
my $newname = $e->{Folder} =~ s/$e->{Hash}/$e->{Name}/r;
say "renaming: $e->{Folder} to:\n$newname";
rename $e->{Folder}, $newname || die $!;
rename $e->{Folder}.'.db', $newname.'.db' || die $!;
return ($newname);
}
main() if $path || die 'Please enter a path to the example files.';
</code></pre>
<h2>Not a web app</h2>
devdata/https_mojolicious.io_blog_2018_12_15_practical-web-content-munging_ view on Meta::CPAN
<p>Notice that the structure of this is regular but not selectable with any one div or piece of markup. I can use the selector <code>h3</code> to get the headings, but the text of each news item is just a paragraph, and we also want to grab the date ...
<p>So, I want to grab all of the titles, and the paragraph following the title, and the date, and put them all into some sort of data structure so I can spit them out into pages of their own.</p>
<p>Let's start with the titles, as it'll show a neat trick Mojo has up its sleeves.</p>
<pre><code>my $main = $tx->res->dom->at('#main');
my @headers = $main->find('h3')->map('text')->each;
</code></pre>
<p>Do you see it? The <code>find</code> method here is returning a <a href="https://mojolicious.org/perldoc/Mojo/Collection">Mojo::Collection</a>. "Collection" is kind of a fancy way to say "list", but these lists have a bunch of ...
<p>After this, <code>@headers</code> will contain all of the titles. There's no way I could do that as simply with regexes (and, we could have chained all of this, including finding <code>#main</code>, into one line, but I'm re-using <code>#m...
<p>Now, an even trickier thing to do with regexes would be to find the immediately subsequent sibling of these headers. But, with Mojo::DOM, we can grab it with just a few more lines of code (there's probably a way to do it with even less code, b...
<pre><code>my @paras;
for my $header ($main->find('h3')->each) {
push (@paras, $header->next->content);
}
</code></pre>
devdata/https_mojolicious.io_blog_2018_12_20_testing-dancer_ view on Meta::CPAN
<pre><code>use Test::Mojo;
my $class = Test::Mojo->with_roles('+PSGI');
</code></pre>
<p>Then you instantiate that role with the path to the PSGI application, or else the PSGI application itself.</p>
<p>Since you're using roles, which are all about composition, you can also apply other roles that you might <a href="https://metacpan.org/search?q=%22Test%3A%3AMojo%3A%3ARole%22">find on CPAN</a>.</p>
<h2>An Example</h2>
<p>As an example, let's say we have a simple application script (named <code>app.psgi</code>) that can render a <code>"hello world"</code> or <code>"hello $user"</code> in several formats.
I'll allow a plain text response, JSON, and templated HTML (using the <a href="https://metacpan.org/pod/Dancer2::Template::Simple">simple</a> template to keep this concise).</p>
<pre><code>use Dancer2;
set template => 'simple';
set views => '.';
any '/text' => sub {
my $name = param('name') // 'world';
send_as plain => "hello $name";
devdata/https_mojolicious.io_blog_2018_12_21_a-little-christmas-template-cooking_ view on Meta::CPAN
<p>The Advent Calendar has shown you many great ways to use Mojolicious, and since you already have Mojo installed you can use it for things besides web processing. Today's recipe uses The templating rendering engine for somethi...
</section>
<section id="section-2">
<p>First, process some string templates. Here's an example lifted from the <a href="https://mojolicious.org/perldoc/Mojo/Template">Mojo::Template</a>, using the <a href="https://www.effectiveperlprogramming.com/2016/12/strip-lea...
<pre><code>use Mojo::Template;
my $mt = Mojo::Template->new;
say $mt->render(<<~'EOF');
% use Time::Piece;
<div>
% my $now = localtime;
Time: <%= $now->hms %>
</div>
EOF
</code></pre>
<p>The lines with leading percent sings are Perl code. One of those lines loads a module, <a href="https://metacpan.org/pod/Time::Piece">Time::Piece</a>, and the other creates the variable <code>$now</code>. The <code><%= %></code> insert value...
<p>You can invert that so that the source of the values comes from outside of the template. Sometimes this is preferable to having too much logic in the presentation layer:</p>
<pre><code>use v5.26;
use Mojo::Template;
my $mt = Mojo::Template->new;
use Time::Piece;
my $now = localtime;
say $mt->render(<<~'EOF', $now->hms );
% my $time = shift;
<div>
Time: <%= $time %>
</div>
EOF
</code></pre>
<p>As seen above, by default, arguments are passed in to the template positionally, which is why the <code>shift</code> is there. I like to describe variables by name rather than their position. The <code>vars</code> attribute allows you to pass a ha...
<pre><code>use v5.26;
use Mojo::Template;
my $mt = Mojo::Template->new->vars(1);
use Time::Piece;
my $now = localtime;
say $mt->render(<<~'EOF', { time => $now->hms } );
<div>
Time: <%= $time %>
</div>
EOF
</code></pre>
<p>It's just as easy to take that template from a file (or many files). This is the sort of thing I used to do this with <a href="http://template-toolkit.org">Template Toolkit</a>, a very fine and capable module that's as good as it ever was....
<p>Typically, Mojo templates use the extension <em>.ep</em>. Loop through all of the files that you specify on the command line and cook the ones that have that extension:</p>
devdata/https_mojolicious.io_blog_2018_12_22_use-carton-for-your-mojolicious-app-deployment_ view on Meta::CPAN
</section>
<section id="section-2">
<p>Indeed, some modules evolve fast (Hello Mojolicious!) which is not bad but can lead to incompatible changes.</p>
<p>ThereâÂÂs also the bugs which can be resolved or introduced in a version and that you encounter if you have the wrong version.</p>
<h2>Cpanfile to the rescue</h2>
<p><a href="https://metacpan.org/pod/cpanfile">Cpanfile</a> is a format for describing CPAN dependencies for Perl applications.</p>
<p>With <code>cpanfile</code>, we can list the modules we need, but we can also force the minimal versions of the modules, their maximum versions⦠or say "I want this exact version of that module".</p>
<p>But we can also list optional modules: you can support different databases, but users shouldnâÂÂt have to install MySQL-related modules if they want to use PostgreSQL.</p>
<p>HereâÂÂs an example of <code>cpanfile</code>:</p>
<pre><code># Do not ask for a specific version
requires 'DateTime';
# Ask a specific version
requires 'Plack', '== 1.0';
# Ask a minimal version
devdata/https_mojolicious.io_blog_2018_12_22_use-carton-for-your-mojolicious-app-deployment_ view on Meta::CPAN
<p>Then, we can install our dependencies with:</p>
<pre><code>carton install
</code></pre>
<p>Our dependencies will be installed in a directory named <code>local</code>.
But there is more: Carton will generate a <code>cpanfile.snapshot</code> file, containing the exact version of our dependencies, allowing us to enforce those exact version (ship it with your application).</p>
<p>In our <code>cpanfile</code> example, we asked for a Mojolicious version greater or equal than 7.0 and lesser than 8.0.
Between the installation on our development server and the installation on the production server, some newer versions of modules we depend on may have been published.
LetâÂÂs say that we have Mojolicious 7.77 in our development environment and 7.90 and that something has changed, which leads to problems (for example, the delay helper from <a href="https://mojolicious.org/perldoc/Mojolicious/Plugin/DefaultHelper...
<p>Both 7.77 and 7.90 versions are in our range, but our application does not work on the production server⦠we need to make the production environment as identical as possible as the development one.</p>
<p>For that, since we have a <code>cpanfile.snapshot</code> file from our development server, we can do:</p>
<pre><code>carton install --deployment
</code></pre>
<p>That installs the exact versions of modules listed in your snapshot.</p>
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
<p>Say you want to write "get this web resource, then print the title".
In blocking code that is easy!</p>
<pre><code>use Mojo::Base -strict, -signatures;
use Mojo::UserAgent;
use Mojo::Util 'trim';
my $ua = Mojo::UserAgent->new;
my $title = trim $ua->get($url)->res->dom->at('title')->text;
say $title;
</code></pre>
<p>In asynchronous code, those two steps don't just follow in sequence, either in execution in the file nor in actual code flow.
A newcomer to nonblocking is told that to make that nonblocking, you need a callback; a thing to do once the transaction is complete.
So they might write this (non-working!) code instead.</p>
<pre><code>use Mojo::Base -strict, -signatures;
use Mojo::IOLoop;
use Mojo::UserAgent;
use Mojo::Util 'trim';
my $ua = Mojo::UserAgent->new;
my $url = 'https://mojolicious.org';
my $title;
$ua->get($url, sub ($ua, $tx) {
$title = trim $tx->res->dom->at('title')->text;
});
say $title;
Mojo::IOLoop->start;
</code></pre>
<p>The problem of course is, the print statement happens before the title is extracted.
In fact the print statement happens before the request is even made!</p>
<p><em>Because there are a lot of examples, I'll skip the first chunk of code.
Assume those lines are always there going foward.</em></p>
<p>The fix in this case is easy</p>
<pre><code>$ua->get($url, sub ($ua, $tx) {
my $title = trim $tx->res->dom->at('title')->text;
say $title;
});
Mojo::IOLoop->start;
</code></pre>
<p>but that isn't always the case.
What if you wanted to return the title rather than print it?
What if wanted to fetch two resources rather than one, whether sequentially or in parallel?</p>
<h2>An Important First Step</h2>
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
<pre><code>use Mojo::Promise;
my $promise = Mojo::Promise->new;
$ua->get($url, sub ($ua, $tx) {
my $title = trim $tx->res->dom->at('title')->text;
$promise->resolve($title);
});
$promise->then(sub ($title) {
say $title;
})->wait;
</code></pre>
<p>At first glance this isn't too much better than the callback.
However, a few nice features emerge.
The most important of which is that the promise object can be returned to the caller and the caller can choose what to do with it.</p>
<p>In useful code you would also want to attach error handling, though I've omitted it here for bevity.
Mojolicious' promise implementation also gives us the <code>wait</code> method to start the ioloop if necessary.</p>
<p>Although it is interesting to see how a user can create a promise object to convert a callback api to promises, many libraries, including Mojolicious, now have promise variants built-in.
Rather than depending on the user to create a promise to resolve in the callback, these libraries will just return a promise of their own.
In the Mojolicious project, by convention methods that return promises end in <code>_p</code>.</p>
<p>With that we can write similar code to the one above</p>
<pre><code>my $promise = $ua->get_p($url);
$promise->then(sub ($tx) {
my $title = trim $tx->res->dom->at('title')->text;
say $title;
})->wait;
</code></pre>
<p>However that's slightly different.
The promise above resolved with the title, this one resolves with the transaction.
That brings us to the next interesting feature of promises: the return value of <code>then</code> is another promise that is resolved with the return value of the callback.
Additionally, if that value is another promise then they chain, if not then it resolves with the value.</p>
<p>We can use that property to replicate the original promise example above more directly like this</p>
<pre><code>my $promise = $ua->get_p($url)->then(sub ($tx) {
return trim $tx->res->dom->at('title')->text;
});
$promise->then(sub ($title) {
say $title;
})->wait;
</code></pre>
<p>This is important if say you had a function that was intended to return a promise that resolved to a title.
Perhaps you might have a function called <code>get_title_p</code> that needs to be called from elsewhere in your project.
Rather than relying on the promise that the user-agent returned, you can now post-process and return the title rather than the HTTP response.</p>
<pre><code>sub get_title_p ($url) {
my $promise = $ua->get_p($url)->then(sub ($tx) {
return trim $tx->res->dom->at('title')->text;
});
return $promise;
}
get_title_p($url)->then(sub ($title) {
say $title;
})->wait;
</code></pre>
<p>All told, this is a step in the right direction, but it still involves a mental shift in style.
Even if this is easier than using pure callbacks, you still have to keep track of promises, consider the implications of chaining.
You still have to attach callbacks using <code>then</code>.
And don't forget error handling callbacks too!</p>
<p><em>Editor's note: to this point in the article, it is similar to the Perl Advent Calendar entry <a href="http://www.perladvent.org/2018/2018-12-19.html">posted just a few days before this one on 2018-12-19</a>, humorously presented by Mark Fo...
If you'd like to see another take on promises and Mojo::Promise specifically, give it a read.
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
<p>What we really wish we could tell the Perl interpreter to do is</p>
<ul>
<li>suspend execution until this promise resolves or is rejected</li>
<li>then move on to handle tasks</li>
<li>when it eventually does resolve or reject</li>
<li>then resume processing right here or throw an exception</li>
</ul>
<p>It is a big ask, but if you could say that, you'd basically get linearity back.
Promises give us the control we'd need for such a mechanism, but until now we in Perl-land lack the ability to suspend and resume the interpreter.
Indeed, some languages already have this mechanism and the result is called the Async/Await pattern.
With a little added magic, howver, we can do just that.</p>
<p>That was a lot of introduction, but now I'm finally ready to introduce <a href="https://metacpan.org/pod/Mojo::AsyncAwait">Mojo::AsyncAwait</a>!</p>
<pre><code>use Mojo::AsyncAwait;
async get_title_p => sub ($url) {
my $tx = await $ua->get_p($url);
return trim $tx->res->dom->at('title')->text;
};
get_title_p($url)->then(sub ($title) {
say $title;
})->wait;
</code></pre>
<p>This code behaves exactly the same as before, but there are some noticable differences.
First we declared <code>get_title_p</code> using the <code>async</code> keyword.
For our purposes this means two things: the function can use the <code>await</code> keyword and it must return a promise.
Of course right away you'll see that I don't return a promise from that function.
Don't worry, it gets wrapped in one automatically if needed.</p>
<p>And what is that <code>await</code> keyword?
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
<pre><code>use Mojo::AsyncAwait;
async get_title_p => sub ($url) {
my $tx = await $ua->get_p($url);
return trim $tx->res->dom->at('title')->text;
};
async main => sub {
my $title = await get_title_p($url);
say $title;
};
main()->wait;
</code></pre>
<p>Of course, if we didn't need the intermediary function anymore, we could skip it.
After all, the first example didn't have <code>get_title_p</code>, it just fetched the url, extracted the title, and printed it.</p>
<pre><code>use Mojo::AsyncAwait;
async main => sub {
my $tx = await $ua->get_p($url);
my $title = trim $tx->res->dom->at('title')->text;
say $title;
};
main()->wait;
</code></pre>
<p>And now that's what we've done too!</p>
<h2>Real World Use</h2>
<p>The above examples were neat, but since they only fetch one url there's no reason to be async.
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
use Mojo::Promise;
async get_title_p => sub ($url) {
my $tx = await $ua->get_p($url);
return trim $tx->res->dom->at('title')->text;
};
async main => sub (@urls) {
my @promises = map { get_title_p($_) } @urls;
my @titles = await Mojo::Promise->all(@promises);
say for map { $_->[0] } @titles;
};
my @urls = (qw(
https://mojolicious.org
https://mojolicious.io
https://metacpan.org
));
main(@urls)->wait;
</code></pre>
<p>Were this code written sequentially, the time it would take to run would be the sum of the time to fetch each url.
However, as written, this will run in approximately the time it takes for the slowest url to respond.</p>
<h3>Web Apps</h3>
<p>Now let's say you wanted to turn that script into a web API.
If we had a webapp that accepted urls as query parameters and returned the responses as JSON it might look like this</p>
<pre><code>use Mojolicious::Lite -signatures;
use Mojo::AsyncAwait;
use Mojo::Promise;
use Mojo::Util 'trim';
plugin 'PromiseActions';
helper get_title_p => async sub ($c, $url) {