A bag of mojo sweets






Diego Kuperman | @freekey

https://diegok.github.io/a-bag-of-mojo-sweets/

Hola!

Mojolicious

Real time web framework



 use Mojolicious::Lite;

 get '/' => { text => 'I ♥ Mojolicious!' };

 app->start;

    

Big distribution

No deps besides perl 5.24

(works with perl >= 5.10.1)

Mojo

Web development toolkit

I ❤️ mojo!


> mojo get https://metacpan.org/release/Mojolicious '.release-modules li' all \
     | grep -P '\S+'
      

  Mojo - Web development toolkit
  Mojo::Asset - HTTP content storage base class
  Mojo::Asset::File - File storage for HTTP content
  Mojo::Asset::Memory - In-memory storage for HTTP content
  Mojo::Base - Minimal base class for Mojo projects
  Mojo::ByteStream - ByteStream
  Mojo::Cache - Naive in-memory cache
  Mojo::Collection - Collection
  Mojo::Content - HTTP content base class
  Mojo::Content::MultiPart - HTTP multipart content
  Mojo::Content::Single - HTTP content
  Mojo::Cookie - HTTP cookie base class
  Mojo::Cookie::Request - HTTP request cookie
  Mojo::Cookie::Response - HTTP response cookie
  Mojo::DOM - Minimalistic HTML/XML DOM parser with CSS selectors
  Mojo::DOM::CSS - CSS selector engine
  Mojo::DOM::HTML - HTML/XML engine
  Mojo::Date - HTTP date
  Mojo::EventEmitter - Event emitter base class
  Mojo::Exception - Exceptions with context
  Mojo::File - File system paths
  Mojo::Headers - HTTP headers
  Mojo::HelloWorld - Hello World!
  Mojo::Home - Home sweet home
  Mojo::IOLoop - Minimalistic event loop
  Mojo::IOLoop::Client - Non-blocking TCP/IP and UNIX domain socket client
  Mojo::IOLoop::Delay - Promises/A+ and flow-control helpers
  Mojo::IOLoop::Server - Non-blocking TCP and UNIX domain socket server
  Mojo::IOLoop::Stream - Non-blocking I/O stream
  Mojo::IOLoop::Subprocess - Subprocesses
  Mojo::IOLoop::TLS - Non-blocking TLS handshake
  Mojo::JSON - Minimalistic JSON
  Mojo::JSON::Pointer - JSON Pointers
  Mojo::Loader - Load all kinds of things
  Mojo::Log - Simple logger
  Mojo::Message - HTTP message base class
  Mojo::Message::Request - HTTP request
      

  Mojo::Message::Response - HTTP response
  Mojo::Parameters - Parameters
  Mojo::Path - Path
  Mojo::Promise - Promises/A+
  Mojo::Reactor - Low-level event reactor base class
  Mojo::Reactor::EV - Low-level event reactor with libev support
  Mojo::Reactor::Poll - Low-level event reactor with poll support
  Mojo::Server - HTTP/WebSocket server base class
  Mojo::Server::CGI - CGI server
  Mojo::Server::Daemon - Non-blocking I/O HTTP and WebSocket server
  Mojo::Server::Hypnotoad - A production web serv...ALL GLORY TO THE HYPNOTOAD!
  Mojo::Server::Morbo - Tonight at 11...DOOOOOOOOOOOOOOOM!
  Mojo::Server::Morbo::Backend - Morbo backend base class
  Mojo::Server::Morbo::Backend::Poll - Morbo default backend
  Mojo::Server::PSGI - PSGI server
  Mojo::Server::Prefork - Pre-forking non-blocking I/O HTTP and WebSocket server
  Mojo::Template - Perl-ish templates
  Mojo::Transaction - Transaction base class
  Mojo::Transaction::HTTP - HTTP transaction
  Mojo::Transaction::WebSocket - WebSocket transaction
  Mojo::URL - Uniform Resource Locator
  Mojo::Upload - Upload
  Mojo::UserAgent - Non-blocking I/O HTTP and WebSocket user agent
  Mojo::UserAgent::CookieJar - Cookie jar for HTTP user agents
  Mojo::UserAgent::Proxy - User agent proxy manager
  Mojo::UserAgent::Server - Application server
  Mojo::UserAgent::Transactor - User agent transactor
  Mojo::Util - Portable utility functions
  Mojo::WebSocket - The WebSocket protocol
  Mojolicious - Real-time web framework
  Mojolicious::Command - Command base class
  Mojolicious::Command::cgi - CGI command
  Mojolicious::Command::cpanify - CPAN-ify command
  Mojolicious::Command::daemon - Daemon command
  Mojolicious::Command::eval - Eval command
  Mojolicious::Command::generate - Generator command
  Mojolicious::Command::generate::app - App generator command
      

  Mojolicious::Command::generate::lite_app - Lite app generator command
  Mojolicious::Command::generate::makefile - Makefile generator command
  Mojolicious::Command::generate::plugin - Plugin generator command
  Mojolicious::Command::get - Get command
  Mojolicious::Command::inflate - Inflate command
  Mojolicious::Command::prefork - Pre-fork command
  Mojolicious::Command::psgi - PSGI command
  Mojolicious::Command::routes - Routes command
  Mojolicious::Command::test - Test command
  Mojolicious::Command::version - Version command
  Mojolicious::Commands - Command line interface
  Mojolicious::Controller - Controller base class
  Mojolicious::Lite - Micro real-time web framework
  Mojolicious::Plugin - Plugin base class
  Mojolicious::Plugin::Config - Perl-ish configuration plugin
  Mojolicious::Plugin::DefaultHelpers - Default helpers plugin
  Mojolicious::Plugin::EPLRenderer - Embedded Perl Lite renderer plugin
  Mojolicious::Plugin::EPRenderer - Embedded Perl renderer plugin
  Mojolicious::Plugin::HeaderCondition - Header condition plugin
  Mojolicious::Plugin::JSONConfig - JSON configuration plugin
  Mojolicious::Plugin::Mount - Application mount plugin
  Mojolicious::Plugin::PODRenderer - POD renderer plugin
  Mojolicious::Plugin::TagHelpers - Tag helpers plugin
  Mojolicious::Plugins - Plugin manager
  Mojolicious::Renderer - Generate dynamic content
  Mojolicious::Routes - Always find your destination with routes
  Mojolicious::Routes::Match - Find routes
  Mojolicious::Routes::Pattern - Route pattern
  Mojolicious::Routes::Route - Route
  Mojolicious::Sessions - Session manager based on signed cookies
  Mojolicious::Static - Serve static files
  Mojolicious::Types - MIME types
  Mojolicious::Validator - Validate values
  Mojolicious::Validator::Validation - Perform validations
  Test::Mojo - Testing Mojo
  ojo - Fun one-liners with Mojo
      

110 libs


> mojo get https://metacpan.org/release/Mojolicious '.release-modules li' all
     | grep -P '\S+' \
     | wc -l
    

Mojolicious

-

WEB

44 libs


> mojo get https://metacpan.org/release/Mojolicious '.release-modules li' all \
     | grep -P '\S+' \
     | grep -Piv 'http|web|mojolicious' \
     | wc -l
    

26 libs


> mojo get https://metacpan.org/release/Mojolicious '.release-modules li' all \
     | grep -P '\S+' \
     | grep -Piv 'http|web|mojolicious' \
     | grep -Piv 'server|useragent|url|upload|test|dom|transaction' \
     | wc -l
    

21 libs


> mojo get https://metacpan.org/release/Mojolicious '.release-modules li' all \
    | grep -P '\S+' \
    | grep -Piv 'http|web|mojolicious' \
    | grep -Piv 'server|useragent|url|upload|test|dom|transaction' \
    | grep -Piv 'path|parameters|helloworld|^\s*ojo' \
    | wc -l
    

    Mojo::Base - Minimal base class for Mojo projects
    Mojo::Cache - Naive in-memory cache
    Mojo::Collection - Collection
    Mojo::EventEmitter - Event emitter base class
    Mojo::Exception - Exceptions with context
    Mojo::Home - Home sweet home
    Mojo::IOLoop - Minimalistic event loop
    Mojo::IOLoop::Delay - Promises/A+ and flow-control helpers
    Mojo::IOLoop::Stream - Non-blocking I/O stream
    Mojo::IOLoop::Subprocess - Subprocesses
    Mojo::IOLoop::TLS - Non-blocking TLS handshake
    Mojo::JSON - Minimalistic JSON
    Mojo::JSON::Pointer - JSON Pointers
    Mojo::Loader - Load all kinds of things
    Mojo::Log - Simple logger
    Mojo::Promise - Promises/A+
    Mojo::Reactor - Low-level event reactor base class
    Mojo::Reactor::EV - Low-level event reactor with libev support
    Mojo::Reactor::Poll - Low-level event reactor with poll support
    Mojo::Template - Perl-ish templates
    Mojo::Util - Portable utility functions
    

Mojo::Base

A simple base class for Mojo projects with fluent interfaces

Mojo::Base


 package Cat;
 use Mojo::Base -base;

 has name => 'Nyan';
 has ['age', 'weight'] => 4;
    

Mojo::Base


 package Tiger;
 use Mojo::Base 'Cat';

 has friend  => sub { Cat->new };
 has stripes => 42;
    

Mojo::Base


 use Mojo::Base -strict;

 my $mew = Cat->new(name => 'Longcat');
 say $mew->age;
 say $mew->age(3)->weight(5)->age;

 my $rawr = Tiger->new(stripes => 38, weight => 250);
 say $rawr->tap(sub { $_->friend->name('Tacgnol') })->weight;
    

Mojo::Base


 # use Mojo::Base -strict;
 use strict;
 use warnings;
 use utf8;
 use feature ':5.10';
 use IO::Handle ();
    

Mojo::Base


 # use Mojo::Base -base;
 use strict;
 use warnings;
 use utf8;
 use feature ':5.10';
 use IO::Handle ();
 push @ISA, 'Mojo::Base';
 sub has { Mojo::Base::attr(__PACKAGE__, @_) }
    

Mojo::Base


 # use Mojo::Base 'SomeBaseClass';
 use strict;
 use warnings;
 use utf8;
 use feature ':5.10';
 use IO::Handle ();
 require SomeBaseClass;
 push @ISA, 'SomeBaseClass';
 sub has { Mojo::Base::attr(__PACKAGE__, @_) }
    

Mojo::Base


 # use Mojo::Base -role;
 use strict;
 use warnings;
 use utf8;
 use feature ':5.10';
 use IO::Handle ();
 use Role::Tiny;
 sub has { Mojo::Base::attr(__PACKAGE__, @_) }
    

Mojo::Base


 my $new_class = SubClass->with_roles('SubClass::Role::One');
 my $new_class = SubClass->with_roles('+One', '+Two');
 $object       = $object->with_roles('+One', '+Two');

 # Create a new class with the role "SubClass::Role::Foo" and instantiate it
 my $new_class = SubClass->with_roles('+Foo');
 my $object    = $new_class->new;
    

Role support depends on Role::Tiny (2.000001+)

Mojo::Base


 # Also enable signatures on 5.20+
 use Mojo::Base -strict, -signatures;
 use Mojo::Base -base, -signatures;
 use Mojo::Base 'SomeBaseClass', -signatures;
 use Mojo::Base -role, -signatures;
    

Mojo::EventEmitter

Mojo::EventEmitter


 package Cat;
 use Mojo::Base 'Mojo::EventEmitter';

 # Emit events
 sub poke {
   my $self = shift;
   $self->emit(roar => 3);
 }

 package main;

 # Subscribe to events
 my $tiger = Cat->new;
 $tiger->on(roar => sub {
   my ($tiger, $times) = @_;
   say 'RAWR!' for 1 .. $times;
 });
 $tiger->poke;

    

Mojo::EventEmitter


 $e->on(error => sub {
   my ($e, $err) = @_;
   say "This looks bad: $err";
 });

 $e = $e->catch(sub {...}); # Same thing, shorter.
     

Special event, fatal if not handled.

Mojo::EventEmitter


 my $cb = $e->once(foo => sub {...});

 # Unsubscribe last subscriber
 $e->unsubscribe(foo => $e->subscribers('foo')->[-1]);

 # Change order of subscribers
 @{$e->subscribers('foo')} = reverse @{$e->subscribers('foo')};
    

Mojo::Exception

Exceptions with context

Mojo::Exception


 use Mojo::Base -strict;
 use Mojo::Exception;

 # Throw exception and show stack trace
 eval { Mojo::Exception->throw('Something went wrong!') };
 say "$_->[0]: $_->[1]($_->[2])" for @{$@->frames};
 say $@->line->[1]; # eval { ...
 say $@->message; # Something went wrong!
 say $@; # Something went wrong!

    

Mojo::Log

Simple logger

Mojo::Log


 use Mojo::Log;

 # Log to STDERR
 my $log = Mojo::Log->new;

 # Customize log file location and minimum log level
 my $log = Mojo::Log->new(path => '/tmp/my.log', level => 'warn');

 my $level = $log->level;
 $log      = $log->level('debug');
 # also $ENV{MOJO_LOG_LEVEL}

 # Log messages
 $log->debug('Not sure what is happening here');
 $log->info('FYI: it happened again');
 $log->warn('This might be a problem');
 $log->error('Garden variety error');
 $log->fatal('Boom');
    

Mojo::Log


 my $cb = $log->format;
 $log->format(sub {
     my ($time, $level, @lines) = @_;
     return "[$level] I ♥ Mojolicious and don't care the message\n";
 });

 $log->on(message => sub {
     my ($log, $level, @lines) = @_;

     # Emitted for every log message
 });
    

Mojo::Log


 my $history = $log->history;
 $log        = $log->history([[time, 'debug', 'That went wrong']]);

 my $size = $log->max_history_size; # 10 by default
 $log     = $log->max_history_size(5);
    

Mojo::Log


 my $handle = $log->handle;
 $log       = $log->handle(IO::Handle->new);

 $log->append("[Wed Aug 15 11:59:04 2018] [YAPC] I ♥ The perl conference\n");

 my $path = $log->path
 $log     = $log->path('/var/log/mojo.log');
    

Mojo::Loader

Load all kinds of things

Mojo::Loader


 use Mojo::Base -strict;
 use Mojo::Loader qw(data_section find_modules load_class);

 # Find modules in a namespace
 for my $module (find_modules 'Some::Namespace') {

   # Load them safely
   if ( my $e = load_class $module ) {
     warn ref $e ? qq{Loading "$module" failed: $e}
                 : qq{"$module" not found!};
     next;
   }
 }
    

 package PerlMongers::Barcelona;
 use Mojo::Base -base;

 has cpan_authors => sub {{ map { chomp; split /\s*=\s*/ } <DATA> }};

 __DATA__
 ALEXM    = Alex Muntada
 DIEGOK   = Diego Kuperman
 ENELL    = Enrique Nell
 FXN      = Xavier Noria
 JAVIER   = Javier Arturo Rodríguez Gutiérrez
 JLMARTIN = José Luis Martínez Torres
 MRUIZ    = Míquel Ruiz Martín
    

Mojo::Loader


 package PerlMongers::Barcelona;
 use Mojo::Base -base;
 use Mojo::Loader qw/ data_section /;

 has cpan_authors => sub {{ map { split /\s*=\s*/ } _lines('authors') }};
 has events       => sub {[ _lines('events') ]};

 sub _lines { split /\n/, data_section(__PACKAGE__ => $_[0]) }

 __DATA__

 @@ authors
 ALEXM    = Alex Muntada
 DIEGOK   = Diego Kuperman
 ENELL    = Enrique Nell
 FXN      = Xavier Noria

 @@ events
 Barcelona Perl & Friends 2017
 Barcelona Perl Workshop 2016
 Barcelona Perl Workshop 2015
 Barcelona Perl Workshop 2014

    

Mojo::Loader


 package PerlMongers::Barcelona;
 use Mojo::Base -base;
 use Mojo::Loader qw/ data_section /;

 has cpan_authors => sub {{ map { split /\s*=\s*/ } _lines('authors') }};
 has events       => sub {[ _lines('events') ]};

 sub _lines { split /\n/, data_section(__PACKAGE__ => $_[0]) }

 __DATA__

 @@ authors (base64)
 QUxFWE0gPSBBbGV4IE11bnRhZGEKRElFR09LID0gRGllZ28gS3VwZXJtYW4KRU5FTEwgPSBFbnJp
 cXVlIE5lbGwKRlhOID0gWGF2aWVyIE5vcmlh

 @@ events (base64)
 QmFyY2Vsb25hIFBlcmwgJiBGcmllbmRzIDIwMTcKQmFyY2Vsb25hIFBlcmwgV29ya3Nob3AgMjAx
 NgpCYXJjZWxvbmEgUGVybCBXb3Jrc2hvcCAyMDE1CkJhcmNlbG9uYSBQZXJsIFdvcmtzaG9wIDIw
 MTQ=

    

Mojo::Template

Perl-ish templates

Mojo::Template


 use Mojo::Base -strict;
 use Mojo::Template;

 # Use Perl modules
 my $mt = Mojo::Template->new;
 say $mt->render(<<'EOF');
 % use Time::Piece;

 % my $now = localtime;
 Time: <%= $now->hms %>
 EOF

 # Render with arguments
 say $mt->render(<<'EOF', [1 .. 13], 'Hello World!');
 % my ($numbers, $title) = @_;
 <%= $title %>
 % for my $i (@$numbers) {
   Test <%= $i %>
 % }
 EOF

 # Render with named variables
 say $mt->vars(1)->render(<<'EOF', {title => 'Hello World!'});
 {
   "title": "<%= $title %>"
 }
 EOF

    

Mojo::Template


 <% Perl code %>

 <%= Perl expression, replaced with result %>

 <%== Perl expression, replaced with XML escaped result %>

 <%# Comment, useful for debugging %>

 <%% Replaced with "<%", useful for generating templates %>



 % Perl code line, treated as "<% line =%>"

 %= Perl expression line, treated as "<%= line %>"

 %== Perl expression line, treated as "<%== line %>"

 %# Comment line, useful for debugging

 %% Replaced with "%", useful for generating templates

    

Mojo::Collection

Array based container for collections

Mojo::Collection


 use Mojo::Base -strict;
 use Mojo::Collection;

 # Manipulate collection
 my $collection = Mojo::Collection->new(qw(just works));
 unshift @$collection, 'it';
 say $collection->join(" "); # it just works

 # Chain methods
 $collection->map(sub { ucfirst })->shuffle->each(sub {
   my ($word, $num) = @_;
   say "$num: $word";
 });

    

Mojo::Collection


 use Mojo::Base -strict;
 use Mojo::Collection qw(c);

 my $nums = c(1 .. 10);

 say $nums->grep(sub{ $_ % 2 })->size; # count odds
 say $nums->map(sub{ $_ % 2 || undef })->compact->size; # same

 say $nums->map(sub{[$_, 100 - $_]})->flatten->reduce(sub{ $a + $b });

    

Mojo::Collection


 c(1 .. 10)->map(sub{ ($_) x $_ })->reduce(sub{ $a + $b });

 c(1 .. 10)->map(sub{ $_ % 2 ? 'odd' : 'even' })->reduce(sub{ $a->{$b}++; $a }, {});

    

Mojo::Collection


 use Mojo::Collection qw(c);

 # "G L A"
 c('A', 'G', 'L', 'D', 'F')->slice(1, 2, 0)->join(' ');

 # "Y A P C"
 c('C', 'Y', 'A', 'D', 'P')->slice(0, 4, 2, 1)->reverse->join(' ');

    

Mojo::Collection


 use Mojo::Collection qw(c);

 c(5..15, 0..10)->uniq->size; # 16

 c(5..15, 0..10)->map(sub{ Car->new(wheels => $_) })
                ->sort(sub{ $a->wheels <=> $b->wheels })
                ->map('wheels')->uniq->to_array;
                # [ 0..16 ]

 c(0..16)->grep(qr/5/)->first; # 5
 c(0..16)->grep(qr/5/)->last; # 15
    

Mojo::File


A scalar-based container for file system paths that provides a friendly API for dealing with different operating systems.

Mojo::File


 use Mojo::Base -strict;
 use Mojo::File;

 my $path = Mojo::File->new('/home/diegok/.vimrc');

 say $path->slurp;     # ...
 say $path->dirname;   # /home/diegok
 say $path->basename;  # .vimrc
 say $path->sibling('.zshrc'); # /home/diegok/.zshrc

    

Mojo::File


 use Mojo::Base -strict;
 use Mojo::File 'path';

 say path('foo/bar')->make_path->child('test.txt')->spurt('Some content')->to_abs;

    

Mojo::File


 use Mojo::Base -strict;
 use Mojo::File 'path';

 say path('/home/diegok/.vim')->list({dir=>1})->join("\n");

 say path('/home/diegok/.vim')->list_tree->grep(qr/README/)->join("\n");

    

Mojo::File


 use Mojo::Base -strict;
 use Mojo::File qw/ path tempdir /;

 my $tmp = tempdir;
 path('/home/diegok/.vim')->list_tree->grep(qr/README/)->each(sub{
    $_->copy_to($tmp)
 });

 say $tmp->list->join("\n");

    

Mojo::File


 use Mojo::Base -strict;
 use Mojo::File qw/ path /;

 # IO::File
 my $handle    = path('some_file')->open('<:encoding(UTF-8)');

 my $handle_rw = path('other_file')->open('+<');
 my $handle_rw = path('other_file')->open('r+'); # same

    

Mojo::Cache

Naive in-memory cache with size limits.

Mojo::Cache


 use Mojo::Cache;

 my $cache = Mojo::Cache->new(max_keys => 50);
 $cache->set(foo => 'bar');
 my $foo = $cache->get('foo');

    

Mojo::Util

Portable utility functions

Mojo::Util


 use Mojo::Util qw/ b64_encode /;
 use Mojo::File qw/ path /;

 my $b64 = b64_encode path('mojo-picon.png')->slurp;
 my $img = b64_decode $b64;
    

Mojo::Util



 package Foo;
 use Mojo::Base -base;
 use Mojo::Util qw/ deprecated extract_usage /;

 =head1 SYNOPSIS

   Usage: Foo->new->help

 =cut

 sub help     { extract_usage }
 sub old_help { deprecated 'old_help is DEPRECATED in favor of help' }

    

Mojo::Util



my $checksum = md5_bytes $bytes;

# "acbd18db4cc2f85cedef654fccc4a4d8"
md5_sum 'foo';

my $checksum = sha1_bytes $bytes;

# "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
sha1_sum 'foo';

    

Mojo::Util



 monkey_patch 'Foo',
   one   => sub { say 'One!' },
   two   => sub { say 'Two!' },
   three => sub { say 'Three!' };

    

Mojo::Util


 # "Some \"word\" to test"
 my $quoted = quote trim '  Some "word" to test  ';

 say unquote $quoted;

 say unindent "  sub {\n    time\n  }\n";

    

Mojo::Util



 use Mojo::Base -strict;
 use Mojo::Util qw/ md5_sum steady_time tablify /;
 use Mojo::File qw/ path /;

 my $t0 = steady_time;

 say tablify path('/usr/bin')->list->map(sub{[
   $_, eval{md5_sum($_->slurp)} || 'Unable to read file'
 ]});

 say 'Took: 'steady_time - $t0, 'secs';

    

Mojo::IOLoop

Minimalistic event loop

Mojo::IOLoop


 use Mojo::Base -strict;
 use Mojo::IOLoop;

 Mojo::IOLoop->server({port => 3003} => sub {
   my ($loop, $stream) = @_;

   $stream->on(read => sub {
     my ($stream, $bytes) = @_;
     say "-> $bytes";
     $stream->write("ack: $bytes");
   });
 });

 Mojo::IOLoop->client({port => 3003} => sub {
   my ($loop, $err, $stream) = @_;

   $stream->on(read => sub { say '<- ', pop });

   my $count = 0;
   $loop->recurring(2 => sub { ++$count && $stream->write("ping($count)") })
 });

 Mojo::IOLoop->timer(10 => sub { shift->stop });

 Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
    

Mojo::IOLoop::Subprocess


 use Mojo::Base -strict, -signatures;
 use Mojo::IOLoop;

 say "Main proccess is '$$'";

 my $subprocess = Mojo::IOLoop->subprocess(

     sub ( $subprocess ) { sleep 5 && return $$ },

     sub ( $subprocess, $err, @results ) {
         say $err ? "Subprocess error: $err"
                  : "Process '$$' got response from '$results[0]'.";

         $subprocess->ioloop->stop;
     }

 );

Mojo::IOLoop->recurring(1 => sub { say "Waiting for '@{[ $subprocess->pid ]}'" });
Mojo::IOLoop->start;
    

Mojo::IOLoop::Delay


 Mojo::IOLoop->delay(
   sub {
     my $delay = shift;
     Mojo::IOLoop->timer(2 => $delay->begin);
     say 'Second step in 2 seconds.';
   },

   sub {
     my $delay = shift;
     Mojo::IOLoop->timer(1 => $delay->begin);
     Mojo::IOLoop->timer(3 => $delay->begin);
     say 'Third step in 3 seconds.';
   },

   sub { say 'And done after 5 seconds total.' }
 )->wait;

    

Mojo::IOLoop::Promise


 use Mojo::Base -strict, -signatures;
 use Mojo::Promise;

 my $promise = Mojo::Promise->new;
 my $loop    = $promise->ioloop;

 $loop->timer(2 => sub{ $promise->resolve(2) });

 $promise->then(sub ( $waited ) {

     my ($t1, $t3) = map { Mojo::Promise->new } 1..2;

     $loop->timer(1 => sub{ $t1->resolve(1 + $waited) });
     $loop->timer(3 => sub{ $t3->resolve(3 + $waited) });

     Mojo::Promise->all($t1, $t3);

 })->then(sub{ say "Done after $_[-1][0] seconds." })->wait;
    

Mojo::IOLoop::Promise


 use Mojo::Base -strict, -signatures;
 use Mojo::Promise;

 my $promise = Mojo::Promise->new;
 my $loop    = $promise->ioloop;

 $loop->timer(2 => sub{ $promise->resolve(2) });

 $promise->then(sub ( $waited ) {

     my ($t1, $t3) = map { Mojo::Promise->new } 1..2;

     $loop->timer(1 => sub{ $t1->resolve(1 + $waited) });
     $loop->timer(3 => sub{ $t3->resolve(3 + $waited) });

     Mojo::Promise->race($t1, $t3);

 })->then(sub{ say "Done after $_[0] seconds." })->wait;
    

...

Thanks!

Questions?

+ Web


 use Mojolicious::Lite -signatures;

 my $hn = Mojo::URL->new('https://news.ycombinator.com/news');

 get '/' => sub($c) {
     $c->ua->get( $hn => sub{
         $c->render( json => pop->res->dom->find('a.storylink')->map(sub{
             [ $_->all_text, $_->attr('href') ]
         })->to_array );
     });
 };

 app->start;
    

 use Mojolicious::Lite -signatures;
 use Mojo::Collection qw(c);

 my $hn = Mojo::URL->new('https://news.ycombinator.com/news');

 get '/:pages' => [ pages => qr/[1-9]\d*/ ] => { pages => 3 } => sub($c) {
     $c->render_later;

     Mojo::Promise->all(
         c( 1 .. $c->stash('pages') )->map(sub{
             $c->ua->get_p( $hn->clone->query( p => $_ ) )->then(sub($tx) {
                 $tx->res->dom->find('a.storylink')->map(sub{
                     [ $_->all_text, $_->attr('href') ]
                 })->to_array;
             })
         })->@*
     )->then(sub{
         $c->render( json => c(@_)->flatten->to_array )
     })->catch(sub{
         $c->render( json => { error => [@_] } )
     });
 };

 app->start;
    

 use Mojolicious::Lite -signatures;
 use Mojo::Collection qw(c);
 use Mojo::Cache;

 my $hn    = Mojo::URL->new('https://news.ycombinator.com/news');
 my $cache = Mojo::Cache->new;

 get '/:pages' => [ pages => qr/[1-9]\d*/ ] => { pages => 3 } => sub($c) {
     $c->render_later;

     Mojo::Promise->all(
         c( 1 .. $c->stash('pages') )->map(sub{
             my $url = $hn->clone->query( p => $_ );

             if ( my $cached = $cache->get($url) ) {
                 return Mojo::Promise->new->resolve( $cached );
             }

             $c->ua->get_p( $url )->then(sub($tx) {
                 my $res = $tx->res->dom->find('a.storylink')->map(sub{
                     [ $_->all_text, $_->attr('href') ]
                 })->to_array;
                 $cache->set( $url => $res );
                 $res;
             })
         })->@*
     )->then(sub{
         $c->render( json => c(@_)->flatten->to_array )
     })->catch(sub{
         $c->render( json => { error => [@_] } )
     });
 };

 app->start;
    

More ?


 use Mojolicious::Lite -signatures;

 my $proxy_url = Mojo::URL->new('https://metacpan.org/');

 any '/*rest' => { rest => '' } => sub($c) {
     my $tx = Mojo::Transaction::HTTP->new( req => $c->req->clone );
     $tx->req->url->$_($proxy_url->$_) for qw/ scheme host port /;
     $tx->req->headers->header( Host => $tx->req->url->host_port );

     $c->render_later;
     $c->ua->start($tx, sub($ua, $tx) {
         $c->tx->res($tx->res);
         $c->rendered;
     });
 };

 app->start;
    

 use Mojolicious::Lite -signatures;
 use Mojo::Cache;

 my $cache     = Mojo::Cache->new;
 my $proxy_url = Mojo::URL->new('https://metacpan.org/');

 any '/*rest' => { rest => '' } => sub($c) {
     my $key = join '=', map {$c->req->$_} qw/ method url /;

     if ( my $res = $cache->get($key) ) {
         return $c->res($res) && $c->rendered;
     }

     my $tx = Mojo::Transaction::HTTP->new( req => $c->req->clone );
     $tx->req->url->$_($proxy_url->$_) for qw/ scheme host port /;
     $tx->req->headers->header( Host => $tx->req->url->host_port );
     $c->render_later;
     $c->ua->start($tx, sub($ua, $tx) {
          $cache->set($key => $tx->res);
          $c->tx->res($tx->res);
          $c->rendered;
     });
 };

 app->start;