SlideShare a Scribd company logo
1 of 115
Download to read offline
ASYNCHRONOUS PROGRAMMING! 
WITH! 
MOJO::IOLOOP::DELAY 
Everything you wished your dad had told you about event loops and asynchronous programming
THE PLAN 
Blocking vs non-blocking! 
Microservices! 
Event loops (!)! 
Event-driven code (")! 
Other options! 
Mojo::IOLoop::Delay 
I promise these are the only emoji characters I’ll use the entire presentation.
Take a break 
LET’S ORDER 
BREAKFAST!! 
pancakes! 
soft-boiled egg! 
orange juice 
Are you hungry? I’m hungry.
BLOCKING CHEF 
mix 
pancakes heat skillet cook 
pancakes boil water cook eggs cut juice 
* Here’s blocking chef: he likes to look busy, so he’s always doing something. He’s going to do everything. 
* And what’s the problem here? It takes too long. What happens? 
* Blocking chef gets fired. Har.
NON-BLOCKING CHEF 
heat skillet cook 
mix 
pancakes 
pancakes 
boil water cook eggs 
cut juice 
Non-blocking chef is smarter and better looking. He can tell the difference between things he has to wait for, and things he can start and then be notified 
when they’re ready. 
! 
NB chef turns on the skillet and puts the water on to boil. He has alarms that tell him when the skillet is hot, when the water has boiled, and when the 
eggs are done.
NUMBERS EVERYONE SHOULD 
KNOW—JEFF DEAN 
L1 cache reference 0.5 ns! 
Branch mispredict 5 ns! 
L2 cache reference 7 ns! 
Mutex lock/unlock 100 ns! 
Main memory reference 100 ns! 
Compress 1K bytes with Zippy 
10,000 ns! 
Send 2K bytes over 1 Gbps 
network 20,000 ns 
Read 1 MB sequentially from memory 
250,000 ns! 
Round trip within same datacenter 
500,000 ns! 
Disk seek 10,000,000 ns 
Read 1 MB sequentially from 
network 10,000,000 ns 
Read 1 MB sequentially from disk 
30,000,000 ns 
Send packet CA→Netherlands→CA 
150,000,000 ns 
Why is this so bad? In human time, 30,000,000 ns doesn’t take long, but in computer time, it’s an eternity. While the network fetch happened, I could have 
been doing millions of cycles of real work. Blocking IO operations are terrible.
MOJOLICIOUS::LITE 
use Mojolicious::Lite; 
! 
get '/foo' => sub { 
my $c = shift; 
$c->render( json => { foo => “bar” }); 
}; 
! 
app->start; 
sets up a “route” (url handler) for GET /foo 
* Here is a controller 
* Here is where we start the event loop 
* Here is a handle to the controller object 
* Here is how we render output to the client (browser, etc.) 
* Here is how we do a JSON serialization of a Perl hash reference 
* It’s all simple and minimal.
MOJOLICIOUS::LITE 
use Mojolicious::Lite; 
! 
get '/foo' => sub { 
my $c = shift; 
$c->render( json => { foo => “bar” }); 
}; 
! 
app->start; 
sets up a “route” (url handler) for GET /foo 
start the daemon and run the event loop 
* Here is a controller 
* Here is where we start the event loop 
* Here is a handle to the controller object 
* Here is how we render output to the client (browser, etc.) 
* Here is how we do a JSON serialization of a Perl hash reference 
* It’s all simple and minimal.
MOJOLICIOUS::LITE 
use Mojolicious::Lite; 
! 
get '/foo' => sub { 
my $c = shift; 
$c->render( json => { foo => “bar” }); 
}; 
! 
app->start; 
sets up a “route” (url handler) for GET /foo 
start the daemon and run the event loop 
$c = “controller” (MVC pattern) 
* Here is a controller 
* Here is where we start the event loop 
* Here is a handle to the controller object 
* Here is how we render output to the client (browser, etc.) 
* Here is how we do a JSON serialization of a Perl hash reference 
* It’s all simple and minimal.
MOJOLICIOUS::LITE 
use Mojolicious::Lite; 
! 
get '/foo' => sub { 
sets up a “route” (url handler) for GET /foo 
start the daemon and run the event loop 
$c = “controller” (MVC pattern) 
invoke render() 
my $c = shift; 
$c->render( json => { foo => “bar” }); 
}; 
! 
app->start; 
* Here is a controller 
* Here is where we start the event loop 
* Here is a handle to the controller object 
* Here is how we render output to the client (browser, etc.) 
* Here is how we do a JSON serialization of a Perl hash reference 
* It’s all simple and minimal.
MOJOLICIOUS::LITE 
use Mojolicious::Lite; 
! 
get '/foo' => sub { 
my $c = shift; 
$c->render( json => { foo => “bar” }); 
}; 
! 
app->start; 
sets up a “route” (url handler) for GET /foo 
start the daemon and run the event loop 
$c = “controller” (MVC pattern) 
invoke render() 
} 
set content-type to application/json and serialize the hashref 
* Here is a controller 
* Here is where we start the event loop 
* Here is a handle to the controller object 
* Here is how we render output to the client (browser, etc.) 
* Here is how we do a JSON serialization of a Perl hash reference 
* It’s all simple and minimal.
MOJOLICIOUS::LITE 
command-line GET! 
start daemon! 
start pre-forking daemon! 
list routes! 
run tests 
$ ./my-app get /foo 
{"foo":"bar"} 
$ ./my-app daemon 
Server available at http://127.0.0.1:3000. 
$ hypnotoad ./my-app 
$ ./my-app routes 
$ ./my-app test 
Some other Mojolicious goodies.
MICROSERVICE ARCHITECTURE 
Microservices: small HTTP (or other TCP) services! 
do one thing, do it well! 
small is beautiful! 
composition! 
text (JSON) is the universal interface! 
Speak HTTP everywhere 
* Microservices are the network extension of the Unix philosophy. 
* Composition: we can compose powerful functions from simpler functions. 
* We’re going to do a little of that tonight.
MICROSERVICE EXAMPLE 
1. get our source IP address! 
2. using the IP address, get our geolocation! 
3. using the geolocation:! 
• get the weather! 
• get the moon phase! 
• get the current air quality index (AQI) 
This is what we’re going to eventually build.
MICROSERVICE: MY IP ADDRESS 
GET http://demo:8082/my-ip 
{"ip":"71.199.21.36"} 
I wrote this simple service to return your source IP, or the IP you’re connecting from.
MICROSERVICE: GEOLOCATION 
GET http://ip-api.com/json/71.199.21.36 
{ 
"query" : "71.199.21.36", 
"country" : "United States", 
"timezone" : "America/Denver", 
"region" : "UT", 
"city" : "American Fork", 
"zip" : “84003", 
"lat" : 40.393, 
"lon" : -111.7838 
} 
This service takes an IP address and returns the location where that IP address originates.
MICROSERVICE: MOON PHASE 
GET http://demo:8080/pom 
{ 
"text" : "94.0%, waning gibbous", 
"phase" : "0.5787", 
"illumination" : "0.9401", 
"age" : "17.0893" 
} 
I wrote this service using the venerable Astro::MoonPhase.
MICROSERVICE: WEATHER 
GET http://demo:8084/weather/40.2960,-111.6946 
{ 
"windBearing" : 85, 
"icon" : "clear-day", 
"pressure" : 1016.52, 
"windSpeed" : 3.73, 
"cloudCover" : 0, 
"summary" : "Clear", 
"dewPoint" : 35.35, 
"humidity" : 0.65, 
"temperature" : 46.41, 
} 
This service is the excellent forecast.io; there’s a ton more information returned here, but the essence of what we’ll be using is here.
MICROSERVICE: AIR QUALITY 
GET http://demo:8083/aqi/40.296/-111.6946 
{ 
"pollutant" : "small particulate matter", 
"index" : 22 
} 
This service gives us the air quality index on a scale of 0 to 500 I believe.
MICROSERVICE: SLOW 
GET http://demo:8081/slow/3 
{"waited":3} 
I wrote this service to show exaggerated latency
A BLOCKING MOJO APP 
use Mojolicious::Lite; 
! 
get '/weather' => sub { 
my $c = shift; 
! 
my $ua = $c->ua; 
! 
my $tx1 = $ua->get('http://demo:8080/pom'); 
my $pom = $tx1->res->json('/text'); 
! 
my $tx2 = $ua->get('http://demo:8081/slow/2'); 
my $slow = $tx2->res->json('/waited'); 
! 
my $tx3 = $ua->get('http://demo:8082/my-ip'); 
my $ip = $tx3->res->json('/ip'); 
! 
$c->render(json => { pom => $pom, 
waited => $slow, 
ip => $ip }); 
}; 
! 
app->start; 
* here’s our app 
* we make a call to POM 
* then we call slow, etc.
HOW DOES IT PERFORM? 
$ wrk -c 1 -d 10 -t 1 http://localhost:3000/weather 
Running 10s test @ http://localhost:3000/weather 
1 threads and 1 connections 
Thread Stats Avg Stdev Max +/- Stdev 
Latency 2.14s 0.00us 2.14s 100.00% 
Req/Sec 0.00 0.00 0.00 100.00% 
4 requests in 10.01s, 0.88KB read 
Socket errors: connect 0, read 0, write 0, timeout 1 
Requests/sec: 0.40 
Transfer/sec: 89.94B 
poorly: 0.4 req/sec 
You might see that I’ve limited the connections down to one; if I went higher than this, wrk’s stats messed up and I couldn’t get any requests per second. 
Even higher, this application blocks and can only handle one connection at a time anyway.
Got any ideas? 
SOLUTIONS? 
hypnotoad! 
still doesn’t scale well… 
Hypnotoad is like Apache: fork off some daemons and hope there are enough of them to handle the load. If we pass our max clients, the next client to 
connect is going to have to wait.
ASYNCHRONOUS EXAMPLE: 
SIGNAL HANDLERS 
$SIG{INT} = sub { say "Got an INT signal!"; exit }; 
! 
while(1) { sleep } 
What is an example of a common asynchronous thing we see when we write programs?
ASYNCHRONOUS EXAMPLE: 
JAVASCRIPT CALLBACKS 
<script type=“text/javascript"> 
! 
$(document).ready(function() { 
jQuery.ajax(“http://www.perl.org”, 
{ 
success: function(data) { 
alert("loaded!") 
} 
}) 
}) 
! 
</script> 
If you’ve used jQuery, you’ve almost certainly written a callback. The browser has an event loop that is continually checking for UI events such as mouse 
clicks, DOM events, or in this case, an ajax load to complete.
ASYNCHRONOUS EXAMPLE: 
JAVASCRIPT CALLBACKS 
<script type=“text/javascript"> 
! 
$(document).ready(function() { 
jQuery.ajax(“http://www.perl.org”, 
{ 
success: function(data) { 
alert("loaded!") 
} 
}) 
}) 
! 
</script> 
If you’ve used jQuery, you’ve almost certainly written a callback. The browser has an event loop that is continually checking for UI events such as mouse 
clicks, DOM events, or in this case, an ajax load to complete.
a- “not” + synchronus “together-time” 
EVENT LOOPS! 
not parallel! 
not threaded! 
event-driven! 
non-blocking 
Speaking of event loops… we’re talking about: 
not parallel programming: multiple cores on one problem 
not threaded programming: multiple threads on one problem 
event-driven: our code runs when events occur 
non-blocking: we don’t have to wait for any I/O
EVENT LOOPS ARE MYSTERIOUS 
Event! 
EV! 
POE! 
IO::Async! 
Mojolicious 
use Event 'loop'; 
my $loop = loop(); 
use EV; 
EV::run; 
use POE; 
POE::Kernel->run(); 
use IO::Async::Loop; 
my $loop = IO::Async::Loop->new; 
$loop->run; 
use Mojolicious::Lite 
app->start; 
Here are some event loops I’ve used over the years, and I never really knew what was going on under the hood. It turns out, it’s pretty simple.
HOW EVENT LOOPS WORK* 
my @events = (); 
my %watchers = ( network => sub { say "Got network data: " . shift } ); 
! 
while(sleep 1) { 
if (my $evt = shift @events) { 
push @events, check_events(); 
my ($event, $data) = @$evt; 
$watchers{$event}->($data); 
if (my $evt = shift @events) { 
} 
! 
! 
my ($event, $data) = @$evt; 
$watchers{$event}->($data); 
check_events(); 
} 
} 
! 
sub check_events { 
if (my $data = network_event() and $watchers{network}) { 
return [network => $data]; 
} 
! 
if (my $data = timer_event() and $watchers{timer}) { 
return [timer => $data]; 
} 
! 
return (); 
} 
* “A little inaccuracy sometimes saves tons of explanation.”—H.H. Munroe 
We loop forever waiting for events to happen. When an event happens, we run some code that was waiting for it and then we loop some more. 
! 
What kinds of events can we watch for? Timers, signals, disk IO, network IO, even custom events that we create ourselves. 
! 
Event loops allow us to get lots of things done while we otherwise might be waiting for slow, blocking things. For example, it only takes a few 
microseconds to build a web request and put it on the wire, but it takes many milliseconds, sometimes seconds, for the response to come back. That's an 
eternity for the computer. Why don't we instead fan out as many requests as we have resources for and then handle the responses when they come back? 
Disk reads are the same: they're often worse than network requests—waiting for I/O in general means you're not getting your money's worth from your 
CPU. Remember blocking chef.
get '/weather' => sub { 
my $c = shift; 
! 
my $tx1 = $c->ua->get(‘http://demo:8080/pom'); 
my $pom = $tx1->res->json('/text'); 
! 
my $tx2 = $c->ua->get(‘http://demo:8081/slow/2'); 
my $slow = $tx2->res->json('/waited'); 
! 
}; 
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
$c->ua->get(‘http://demo:8080/pom' => sub { 
my $tx = pop; 
my $pom = $tx->res->json('/text'); 
! 
$c->ua->get(‘http://demo:8081/slow/2' => sub { 
my $slow = pop->res->json('/waited'); 
... 
}); 
}); 
}; 
blocking 
non-blocking 
Here’s our same application but converted to non-blocking mode using callbacks. 
! 
In event loops, functions no longer return anything, or at least anything useful. All of the useful stuff is passed to the callback instead. 
! 
In event loops, when we want to force an ordering, we have to put the dependent code inside the callback also. 
! 
Note that event-driven code doesn’t run faster, it just doesn’t block. An individual request still takes the same amount of time, but the event loop isn’t 
blocked while waiting for slow requests, and can handle many more at the same time.
HOW DOES IT PERFORM? 
$ wrk -c 10000 -d 10 -t 100 http://localhost:3000/weather 
Running 10s test @ http://localhost:3000/weather 
100 threads and 10000 connections 
Thread Stats Avg Stdev Max +/- Stdev 
Latency 3.31s 1.87s 7.88s 51.25% 
Req/Sec 28.50 17.21 53.00 75.00% 
1314 requests in 10.01s, 258.28KB read 
Socket errors: connect 0, read 12, write 0, timeout 47624 
Requests/sec: 131.31 
Transfer/sec: 25.81KB 
better: 131 req/sec! 
300x speedup! 
* note: each individual request still takes 2s, but we can now handle hundreds or thousands of these simultaneously because we’re not blocking on the 
slow calls.
DRAWBACKS 
Continuation-passing style! 
event-loop concurrency! 
flow control, error handling 
* CPS is hard to read and work with 
* We achieve concurrency only in the main event loop, not within the handler itself; e.g., even if we had things that *could* happen at the same time, we 
can’t do that. 
* Control over flow
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
my $ua = $c->ua; 
! 
$ua->get('http://demo:8080/pom' => sub { 
my $tx = pop; 
my $pom = $tx->res->json('/text'); 
! 
$ua->get('http://demo:8081/slow/2' => sub { 
my $tx = pop; 
my $slow = $tx->res->json(‘/waited'); 
! 
$ua->get('http://demo:8082/my-ip' => sub { 
my $tx = pop; 
my $ip = $tx->res->json(‘/ip'); 
! 
$c->render(json => { pom => $pom, 
waited => $slow, 
ip => $ip }, 
status => 200); 
}); 
}); 
}); 
}; 
This is a picture of the famous pyramid of death aka callback-hell.
Thanks to tempire for the meme
What now? 
OTHER 
SOLUTIONS 
AnyEvent! 
Promises! 
Mojo::IOLoop::Delay
ANYEVENT PRIMER 
ad-hoc flow control! 
powerful primitives 
CondVars give us ad hoc flow control in any event loop. They’re powerful primitives and you can build your own event system on top of them.
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 0 
cb: undef 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 0 
cb: undef 
$cv->cb(sub { say “all urls done” }); 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 0 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 0 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 1 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
=> sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 1 
cb: sub { … } 
$ua->get($url1 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
=> sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 1 
cb: sub { … } 
$ua->get($url1 
$ fetch-urls 
$url1 request… 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
=> sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 1 
cb: sub { … } 
$ua->get($url1 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 1 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 
=> sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 
=> sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
$url2 request… 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 
=> sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
running... 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
$ fetch-urls 
running... 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv wait… 
counter: 2 
cb: sub { … } 
$ fetch-urls 
running... 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv wait… 
counter: 2 
cb: sub { … } 
receive $url2 response 
$ fetch-urls 
running... 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
} 
wait… 
receive $url2 response 
run get() callback 
$ fetch-urls 
running... 
url2 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
$cv 
counter: 2 
cb: sub { … } 
} 
wait… 
receive $url2 response 
run get() callback 
$cv->end 
$ fetch-urls 
running... 
url2 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
wait… 
receive $url2 response 
run get() callback 
$cv 
counter: 1 $cv->end 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
$cv 
counter: 1 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => sub { say “url1 done”; 
$cv->end }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
receive $url1 response 
$cv 
counter: 1 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
sub { say “url1 done”; 
$cv->end } wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
receive $url1 response 
run get() callback 
$cv 
counter: 1 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
url1 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
sub { say “url1 done”; 
$cv->end } wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
receive $url1 response 
run get() callback 
$cv->end 
$cv 
counter: 1 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
url1 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
sub { say “url1 done”; 
$cv->end } wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
receive $url1 response 
run get() callback 
$cv 
counter: 0 $cv->end 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
url1 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(sub { say “all urls done” }); 
!! 
$cv->begin; 
$ua->get($url1 => }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
sub { say “url1 done”; 
$cv->end } wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
receive $url1 response 
run get() callback 
$cv 
counter: 0 $cv->end 
cb: sub { … } 
$ fetch-urls 
running... 
url2 done 
url1 done 
(walk through as if you were the Perl interpreter)
use AnyEvent; 
use EV; 
! 
my $cv = AE::cv; 
! 
$cv->cb(}); 
!! 
sub { say “all urls done” } 
$cv->begin; 
$ua->get($url1 => }); 
! 
$cv->begin; 
$ua->get($url2 => sub { say “url2 done”; 
$cv->end }); 
! 
say “running...” 
EV::run 
sub { say “url1 done”; 
$cv->end } wait… 
receive $url2 response 
run get() callback 
$cv->end 
wait… 
receive $url1 response 
run get() callback 
$cv 
counter: 0 $cv->end 
cb: sub { … } 
$ fetch-urls 
$ fetch-urls 
running... 
running... 
url2 done 
url1 done 
all urls done 
(walk through as if you were the Perl interpreter)
use Mojolicious::Lite; 
use AnyEvent; 
! 
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
my %res = (); 
! 
my $cv = AnyEvent->condvar; 
$cv->cb(sub { $c->render( json => %res, status => 200 ) } ); 
! 
$cv->begin; 
$c->ua->get( 'http://demo:8080/pom' => 
sub { $res{pom} = pop->res->json('/text'); $cv->end } ); 
! 
$cv->begin; 
$c->ua->get( 'http://demo:8081/slow/2' => 
sub { $res{slow} = pop->res->json('/waited'); $cv->end } ); 
! 
$cv->begin; 
$c->ua->get( 'http://demo:8082/my-ip' => 
sub { $res{ip} = pop->res->json('/ip'); $cv->end } ); 
}; 
! 
app->start; 
ANYEVENT 
* we’ll set up a hash to catch responses, make condvar, etc. 
* This is raw AnyEvent programming 
* We could add lots of abstractions here to make this cleaner 
* We avoid CPS, but we have global %res 
* Performs well
ANYEVENT REVIEW 
primitives! 
flow! 
flexible! 
framework agnostic 
* AnyEvent provides low-level event primitives with condvars 
* flow is hard to see 
* 100% flexible to any kind of pattern, but you have to DIY 
* powerful and fast
use Mojolicious::Lite; 
use Promises backend => ['EV'], qw/deferred collect/; 
! 
helper get_url => sub { 
my $c = shift; 
my $url = shift; 
my $deferred = deferred; 
$c->ua->get($url => sub { $deferred->resolve( pop->res->json ) }); 
$deferred->promise; 
}; 
! 
get '/weather' => sub { 
my $c = shift; 
! 
collect( 
$c->get_url('http://demo:8080/pom'), 
$c->get_url('http://demo:8081/slow/2'), 
$c->get_url('http://demo:8082/my-ip'), 
)->then( 
sub { 
my ( $pom, $slow, $ip ) = map { $_->[0] } @_; 
$c->render(json => {pom => $pom->{text}, 
ip => $ip->{ip}, 
slow => $slow->{waited}}); 
}, 
sub { warn @_ } 
); 
}; 
! 
app->start; 
PROMISES 
* The Promises module is nice if you’re coming from a JavaScript background 
* Better built-in error handling than AE 
* Better flow than AE out of the box 
* Performs well 
* Requires named functions to avoid lots of boilerplate
PROMISES REVIEW 
additional abstractions! 
promises pattern! 
framework agnostic 
* Additional abstractions: as you move toward functional programming, Promises gets complex, but not as bad as callbacks 
* The promises pattern is not always intuitive 
* Hard to change from blocking to promises (my experience) 
* Lots to love, actually—framework agnostic
And now the moment you’ve all been waiting 
for is here… 
MOJO::! 
IOLOOP::DELAY 
Mojo::IOLoop::Delay combines the flexibility of condvars and the ease of use of Promises.
CALLBACK PROBLEMS 
callback hell! 
continuation-passing style! 
flow control
MOJO::IOLOOP::DELAY++ 
flat call chains! 
powerful primitives like AnyEvent! 
clean abstractions like Promises! 
mix-n-match sequential/concurrent patterns 
* Ideal choice for Mojolicious applications
BOILERPLATE 
my $delay = Mojo::IOLoop::Delay->new; 
$delay->steps( 
sub { 
my $d = shift; 
(non-blocking call) 
}, 
sub { 
my $d = shift; 
(non-blocking results) 
}, 
); 
* we create the $delay object first 
* we call its steps() method 
* steps always happen sequentially
STEPS 
Steps are just subroutines you define! 
Each step receives the $delay object and all of the arguments 
passed to it from the previous step! 
The next step will not run until:! 
the current step has created one or more step callbacks! 
each step callback has been invoked
CALLBACK REDUX 
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
$c->ua->get(‘http://demo:8080/pom' => sub { 
my $pom = $tx->res->json('/text'); 
! 
$c->ua->get(‘http://demo:8081/slow/2' => sub { 
my ($ua, $tx) = @_; 
my $slow = $tx->res->json('/waited'); 
... 
}); 
}); 
}; 
my ($ua, $tx) = @_; 
* Remember that the get() callback receives the useragent object and the transaction object.
STEP PATTERNS 
$delay->steps( 
sub { 
my $d = shift; 
! 
$c->ua->get($url => sub { $end->(@_, ‘foo’) }); 
}, 
sub { 
my $d = shift; 
my ($ua, $tx, $foo) = @_; 
! 
... 
}, 
); 
my $end = $d->begin(0); 
make a step callback 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* our useragent callback is smaller 
* we move the result processing down to the next step 
* step callbacks pass their arguments to the next step
STEP PATTERNS 
$delay->steps( 
sub { 
invoke the step callback 
my $d = shift; 
my $end = $d->begin(0); 
! 
$c->ua->get($url => sub { }); 
}, 
sub { 
my $d = shift; 
my ($ua, $tx, $foo) = @_; 
! 
... 
}, 
); 
$end->(@_, ‘foo’) 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* our useragent callback is smaller 
* we move the result processing down to the next step 
* step callbacks pass their arguments to the next step
STEP PATTERNS 
$delay->steps( 
sub { 
my $d = shift; 
my $end = $d->begin(0); 
! 
$c->ua->get($url => sub { $end->(@_, foo’) }); 
}, 
sub { 
my $d = shift; 
my ($ua, $tx, $foo) = @_; 
! 
... 
}, 
); 
@_, ‘foo’ 
step callback arguments! 
pass to the next step 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* our useragent callback is smaller 
* we move the result processing down to the next step 
* step callbacks pass their arguments to the next step
STEP PATTERNS 
$delay->steps( 
sub { 
my $d = shift; 
my $end = $d->begin(0); 
! 
$c->ua->get($url => sub { $end->(@_, ‘foo’) }); 
}, 
sub { 
my $d = shift; 
! 
... 
}, 
); 
my ($ua, $tx, $foo) = @_; 
step callback arguments! 
pass to the next step 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* our useragent callback is smaller 
* we move the result processing down to the next step 
* step callbacks pass their arguments to the next step
COMPARE TO CONDVARS 
AnyEvent condition variables have begin() and end() 
methods! 
Mojo::IOLoop::Delay objects have only a begin() method 
which returns a subroutine reference. It is roughly equivalent to 
this using AnyEvent condvars: 
sub begin { 
my $cv = shift; 
$cv->{counter}++; 
return sub { $cv->end }; 
} 
* If we were to implement Mojo::IOLoop::Delay using AnyEvent primitives, we might come up with something like this.
STEP PATTERNS 
$delay->steps( 
sub { 
my $d = shift; 
! 
$c->ua->get($url => sub { $end->(@_) }); 
}, 
sub { 
my $d = shift; 
my ($tx) = @_; 
! 
... 
}, 
); 
my $end = $d->begin(1); 
make a step callback 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* the (1) argument tells begin() how many arguments to shift off 
* this makes our second step cleaner 
* you can see our second step now only receives the $tx, not the $ua
STEP PATTERNS 
$delay->steps( 
sub { 
invoke the step callback 
my $d = shift; 
my $end = $d->begin(1); 
! 
$c->ua->get($url => sub { }); 
}, 
sub { 
my $d = shift; 
my ($tx) = @_; 
! 
... 
}, 
); 
$end->(@_) 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* the (1) argument tells begin() how many arguments to shift off 
* this makes our second step cleaner 
* you can see our second step now only receives the $tx, not the $ua
STEP PATTERNS 
$delay->steps( 
sub { 
my $d = shift; 
my $end = $d->begin(1); 
! 
$c->ua->get($url => sub { $end->(@_) }); 
}, 
sub { 
my $d = shift; 
! 
... 
}, 
); 
my ($tx) = @_; 
* we create the step callback at the start of the step 
* we invoke step callback inside the non-blocking callback 
* the (1) argument tells begin() how many arguments to shift off 
* this makes our second step cleaner 
* you can see our second step now only receives the $tx, not the $ua
STEP PATTERNS 
$delay->steps( 
sub { 
my $d = shift; 
! 
$c->ua->get($url => sub { $end->(@_) }); 
}, 
sub { 
my $d = shift; 
my ($tx) = @_; 
! 
... 
}, 
); 
my $end = $d->begin; 
make a step callback 
* it turns out that “1” is the default for begin(); we can leave it off 
* Note that Mojo::IOLoop::Delay’s begin() function is richer than AnyEvent’s—it not only increments an internal counter, but it also returns the very 
callback to invoke to decrement the counter.
STEP PATTERNS 
$delay->steps( 
sub { 
invoke the step callback 
my $d = shift; 
my $end = $d->begin; 
! 
$c->ua->get($url => sub { }); 
}, 
sub { 
my $d = shift; 
my ($tx) = @_; 
! 
... 
}, 
); 
$end->(@_) 
* it turns out that “1” is the default for begin(); we can leave it off 
* Note that Mojo::IOLoop::Delay’s begin() function is richer than AnyEvent’s—it not only increments an internal counter, but it also returns the very 
callback to invoke to decrement the counter.
STEP PATTERNS 
$d->begin creates a step callback 
$delay->steps( 
sub { 
my $d = shift; 
$c->ua->get($url => begin); 
}, 
sub { 
my $d = shift; 
my $tx = shift; 
! 
... 
}, 
); 
$d->begin 
The step callback is invoked when the! 
user-agent transaction response happens. 
* If all you’re doing in your non-blocking callback is invoking the step callback, you can save yourself some time and boilerplate 
* This is a common pattern
GETTING TO THE NEXT STEP 
steps that make asynchronous calls must create a step callback 
before the asynchronous call, then invoke it after the call 
completes (i.e., in the callback): 
sub { 
my $d = shift; 
my $end = $d->begin(1); 
$c->ua->get($url => sub { $end->(@_) }); 
} 
sub { 
my $d = shift; 
my $end = $d->begin; 
$c->ua->get($url => $end); 
} 
or save yourself some typing and create the step callback in the 
asynchronous call: 
sub { 
my $d = shift; 
$c->ua->get($url => $d->begin); 
} 
* these are functionally equivalent 
* use the top left version if you need to modify the arguments before they’re passed to the next step 
* use the bottom version for clarity 
* remember: begin() creates a step callback!
GETTING TO THE NEXT STEP 
steps that do not make asynchronous calls may use pass() 
sub { 
my $d = shift; 
$d->pass(@stuff); 
} 
sub { 
my $d = shift; 
0)->(@stuff); 
} 
$d->begin(0) 
* This lower one is interesting: we make a step callback here (click), then we immediately invoke it 
* If you’re not familiar with functional programming in Perl, this can be a little mind bending.
GETTING TO THE NEXT STEP 
steps that do not make asynchronous calls may use pass() 
sub { 
my $d = shift; 
$d->pass(@stuff); 
} 
sub { 
my $d = shift; 
$d->begin(0)->(@stuff); 
} 
* This lower one is interesting: we make a step callback here (click), then we immediately invoke it 
* If you’re not familiar with functional programming in Perl, this can be a little mind bending.
QUIZ: 1 OF 4 
$delay->steps( 
sub { 
my $d = shift; 
! 
$c->ua->get($url => $d->begin); 
}, 
sub { 
my $d = shift; 
my (?) = @_; 
! 
... 
}, 
); 
## $tx 
Will the second step ever be called? What arguments will it receive?
QUIZ: 2 OF 4 
$delay->steps( 
sub { 
my $d = shift; 
! 
$c->ua->get($url => sub { $d->begin }); 
}, 
sub { 
my $d = shift; 
my (?) = @_; 
! 
... 
}, 
); 
Will the second step ever be called? What arguments will it receive? 
Not reached! The steps callback created by $d->begin isn’t invoked until the non-blocking callback runs and by then the delay has stopped. The step 
callback must be created before the non-blocking call is made.
QUIZ: 3 OF 4 
$delay->steps( 
sub { 
my $d = shift; 
my $end = $d->begin(0); 
! 
$c->ua->get($url => sub { $end->(@_) }); 
}, 
sub { 
my $d = shift; 
my (?) = @_; 
! 
... 
}, 
); 
## ($ua, $tx) 
Will the second step ever be called? What arguments will it receive?
QUIZ: 4 OF 4 
$delay->steps( 
sub { 
my $d = shift; 
my $end = $d->begin; 
! 
$c->ua->get($url => sub { $end->(@_) }); 
}, 
sub { 
my $d = shift; 
my (?) = @_; 
! 
... 
}, 
); 
## $tx 
Will the second step ever be called? What arguments will it receive?
QUIZ: 5 OF 4 
$delay->steps( 
sub { 
my $d = shift; 
my $end = $d->begin(0); 
! 
$c->ua->get($url => sub { $end->(@_) if @_ }); 
}, 
sub { 
my $d = shift; 
my (?) = @_; 
! 
... 
}, 
); 
Will the second step ever be called? What arguments will it receive? 
* Maybe, if there are arguments from the callback. 
* This is risky. 
* remember: you must invoke $d->begin or $d->pass before you leave the step
QUIZ: EXTRA CREDIT 
$delay->steps( 
sub { 
my $d = shift; 
$d->pass(1, 2, 3); 
}, 
sub { 
my $d = shift; 
my (@nums) = @_; 
! 
... 
}, 
); 
Will the second step ever be called? What arguments will it receive?
QUIZ: EXTRA CREDIT 
$delay->steps( 
sub { 
my $d = shift; 
$d->begin(1, 2, 3); 
}, 
sub { 
my $d = shift; 
my (@nums) = @_; 
! 
... 
}, 
); 
Will the second step ever be called? What arguments will it receive? 
* Trick question! The answer is “no” because the step callback is never invoked.
QUIZ: EXTRA CREDIT 
$delay->steps( 
sub { 
my $d = shift; 
$d->begin->(1, 2, 3); 
}, 
sub { 
my $d = shift; 
my (@nums) = @_; 
! 
... 
}, 
); 
Will the second step ever be called? What arguments will it receive?
QUIZ: EXTRA CREDIT 
$delay->steps( 
sub { 
my $d = shift; 
$d->begin(0)->(1, 2, 3); 
}, 
sub { 
my $d = shift; 
my (@nums) = @_; 
! 
... 
}, 
); 
Will the second step ever be called? What arguments will it receive?
Good job! 
FINISHING UP
CONCURRENCY 
use Mojolicious::Lite; 
! 
get '/weather' => sub { 
my $c = shift; 
! 
Mojo::IOLoop::Delay->new->steps( 
sub { 
my $d = shift; 
$c->ua->get( 'http://demo:8080/pom' => $d->begin ); 
$c->ua->get( 'http://demo:8081/slow/2' => $d->begin ); 
$c->ua->get( 'http://demo:8082/my-ip' => $d->begin ); 
}, 
sub { 
my $d = shift; 
$c->render( json => { 
moon => shift->res->json('/text'), 
slow => shift->res->json('/waited'), 
ip => shift->res->json('/ip') 
}); 
} 
); 
}; 
! 
app->start; 
* We can make multiple step callbacks! The next step will not run until all step callbacks have been invoked. 
* Notice how succinct our non-blocking callbacks are. We can make multiple, non-blocking calls in the same step, and they’re all run concurrently. 
* The next step will receive the results in the order they were invoked. In this case, we get three transaction objects and we can just shift them off in our 
render()
SERIALIZATION 
Mojo::IOLoop::Delay->new->steps( 
sub { 
my $d = shift; 
$c->ua->get( 'http://demo:8082/my-ip' => $d->begin ); 
}, 
sub { 
my $d = shift; 
my $ip = shift->res->json('/ip'); 
! 
$c->ua->get( "http://demo:8080/pom?ip=$ip" => $d->begin ); 
}, 
sub { 
my $d = shift; 
my $pom = shift->res->json('/text'); 
! 
$c->ua->get( 'http://demo:8081/slow/2' => $d->begin ); 
}, 
sub { 
my $d = shift; 
$c->render( json => { slow => shift->res->json('/waited') } ); 
} 
); 
* Here we demonstrate how do make non-blocking calls in a particular order, and only after the previous non-blocking call has finished. 
* We might want to do this if we have a call to make that depends on a previous non-blocking call. This is pretty common. 
* You can see the chain of events, and passed arguments.
MICROSERVICE EXAMPLE 
1. get our source IP address! 
2. using the IP address, get our geolocation! 
3. using the geolocation:! 
• get the weather! 
• get the moon phase! 
• get the current air quality index (AQI)
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
Mojo::IOLoop::Delay->new->steps( 
sub { 
$c->ua->get('http://demo:8082/my-ip' => shift->begin); 
}, 
! 
sub { 
my $d = shift; 
my $ip = shift->res->json('/ip'); 
$c->ua->get("http://ip-api.com/json/$ip" => $d->begin); 
}, 
! 
sub { 
my $d = shift; 
my $tx = shift; 
my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); 
! 
$c->ua->get('http://demo:8080/pom' => $d->begin); 
$c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); 
$c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); 
}, 
! 
sub { 
my $d = shift; 
! 
$c->render(json => { moon => shift->res->json('/text'), 
aqi => shift->res->json('/index'), 
temp => shift->res->json('/temp') }); 
} 
); 
}; 
* Here is the final example; we’re pulling out all the stops and making a mixture of sequential, dependent calls and concurrent, independent calls. 
* This isn’t bad, and I feel yields one of the clearest and least boilerplate-y approaches to event loop programming. 
* But what bothers me here is we have to pass our arguments from one step to another, or we have to use a variable outside of the delay object. 
* We can do a better.
DELAY “STASH” VIA DATA() 
scratch space! 
keeps state 
* data() gives us a scratch space or “stash” attached to the delay object 
* it keeps state for the life of the delay object, then cleans up when the delay is finished 
* solves the AnyEvent problem of keeping around external data structures 
* solves the step() problem of having to collect and pass arguments from step to step
Mojo::IOLoop::Delay->new->steps( 
sub { 
$c->ua->get('http://demo:8082/my-ip' => shift->begin); 
}, 
! 
sub { 
my $d = shift; 
$c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); 
}, 
! 
sub { 
my $d = shift; 
my $tx = shift; 
my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); 
$d->data(lat => $lat, lon = $lon); 
! 
$c->ua->get('http://demo:8080/pom' => $d->begin); 
$c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); 
$c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); 
}, 
! 
sub { 
my $d = shift; 
! 
$c->render(json => $d->data); ## also includes ip, lat, lon 
} 
); 
$d->data(ip => shift->res->json('/ip')); 
$d->data( moon => shift->res->json('/text'), 
aqi => shift->res->json('/index'), 
temp => shift->res->json('/temp') ); 
* here we store the results of each step in the data() method for the delay 
* this adds a little bulk without much benefit here 
* but it allows us to do other interesting things
Mojo::IOLoop::Delay->new->steps( 
sub { 
$c->ua->get('http://demo:8082/my-ip' => shift->begin); 
}, 
! 
sub { 
my $d = shift; 
$d->data(ip => shift->res->json('/ip')); 
$c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); 
}, 
! 
sub { 
my $d = shift; 
my $tx = shift; 
my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); 
$d->data(lat => $lat, lon = $lon); 
! 
$c->ua->get('http://demo:8080/pom' => $d->begin); 
$c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); 
$c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); 
}, 
! 
sub { 
my $d = shift; 
$d->data( moon => shift->res->json('/text'), 
aqi => shift->res->json('/index'), 
temp => shift->res->json('/temp') ); 
! 
$c->render(json => $d->data); 
## also includes ip, lat, lon 
} 
); 
* here we store the results of each step in the data() method for the delay 
* this adds a little bulk without much benefit here 
* but it allows us to do other interesting things
ABSTRACTING STEPS 
helper req_steps => sub { 
my ( $c, $name, $url ) = @_; 
( 
sub { $c->ua->get( $url => shift->begin ) }, 
sub { shift->data( $name => shift->res->json("/$name") )->pass } 
); 
}; 
! 
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
Mojo::IOLoop::Delay->new->steps( 
$c->req_steps( text => 'http://demo:8080/pom' ), 
$c->req_steps( waited => 'http://demo:8081/slow/2' ), 
$c->req_steps( ip => 'http://demo:8082/my-ip' ), 
sub { 
my $d = shift; 
$c->render( json => $d->data ); 
} 
); 
}; 
* here we have a helper that returns two steps 
* the first step makes a non-blocking call to the URL we specify 
* the second step stores the results in data() with the key we gave it 
* then in our controller, we call the helper for each URL, and in the last step we collect the results and render 
* this is great if your HTTP requests are simple, but we can do even better
ABSTRACTING DELAYS 
Can we call a delay from within a step? 
* the Mojo::IOLoop is a singleton, so all events from all Delay objects are sent to the same place
IDEAL FRAMEWORK 
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
Mojo::IOLoop::Delay->new->steps( 
sub { get geolocation }, 
sub { get weather }, 
sub { 
my $d = shift; 
$c->render( json => shift ); 
} 
); 
}; 
* here’s our framework we want in pseudo-code 
* get the geo-location 
* get the weather 
* render the response 
* remember: steps receive data from the previous step if it was passed via pass() or the step callback. 
* what does get geolocation look like?
GEOLOCATION STEP 
helper 'get.geolocation' => sub { 
my $c = shift; 
! 
sub { 
my $delay = shift; 
my $data = shift; 
my $delay_end = $delay->begin(0); 
! 
Mojo::IOLoop::Delay->new->steps( 
sub { 
$c->ua->get('http://demo:8082/my-ip' => shift->begin); 
}, 
sub { 
my $d = shift; 
$d->data(ip => shift->res->json('/ip')); 
$c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); 
}, 
sub { 
my $d = shift; 
my $tx = shift; 
$d->data(lat => $tx->res->json('/lat'), 
lon => $tx->res->json('/lon')); 
$delay->data('get.geolocation' => $d->data); 
$delay_end->($d->data); 
} 
); 
} 
}; 
* Here is our helper; remember that this helper will be running inside of a Delay object, so it needs to return a step, or sub ref. The step receives the 
delay object, and possibly some data from the previous step, which we can’t see here. We then run $delay->begin(0) since we may be making non-blocking 
calls and we want to not move on to the next step until they’re all done. 
* Now we create our own delay object to: 
* get the IP 
* get the geolocation based on the IP 
* store the IP and lat/long into the delay data() store 
* pass that delay data store to the outer delay data store 
* pass the delay data store along to the next step, which we haven’t written yet
WEATHER STEP 
Mojo::IOLoop::Delay->new->steps( 
sub { 
my $d = shift; 
my $tx = shift; 
my ( $lat, $lon ) = ( $data->{lat}, $data->{lon} ); 
! 
$c->ua->get('http://demo:8080/pom' => $d->begin); 
$c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); 
$c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); 
}, 
sub { 
my $d = shift; 
my ($pom_tx, $aqi_tx, $weather_tx) = @_; 
! 
$d->data(pom => $pom_tx->res->json(‘/text'), 
aqi => $aqi_tx->res->json(‘/index'), 
temp => $weather_tx->res->json('/currently/temperature'), 
summary => $weather_tx->res->json('/currently/summary')); 
$delay->data('get.weather' => $d->data); 
$delay_end->($d->data); 
} 
); 
* this weather step follows exactly the same pattern 
* we create a step/subref (not shown) 
* in the step we create our own Delay object 
* the first step uses the data from the “previous” step, which was our geolocation step 
* the last step marshals all the responses and returns another data structure
OUTER DELAY AND 
CONTROLLER 
get '/weather' => sub { 
my $c = shift; 
$c->render_later; 
! 
Mojo::IOLoop::Delay->new->steps( 
$c->get->geolocation, 
$c->get->weather, 
sub { 
my $d = shift; 
$c->render( json => shift ); 
} 
); 
}; 
* Now we’re cooking with panache! 
* We can see the power of this pattern: 
* we could build libraries of these functions 
* each has its own function signatures: expected input and outputs, those could be checked, add error handling, etc. 
* The price of this simplicity was a bit of boilerplate in the helpers, but that could be easily abstracted with a plugin 
* tempire will get right on that
MOJO::IOLOOP::DELAY 
is teh awesome!
HIRE ME! 
I’m looking for something great—with Perl!! 
Email: scott@perlcode.org! 
Voice/sms: +1.801.796.2484

More Related Content

What's hot

Node js overview
Node js overviewNode js overview
Node js overviewEyal Vardi
 
Spring Boot to Quarkus: A real app migration experience | DevNation Tech Talk
Spring Boot to Quarkus: A real app migration experience | DevNation Tech TalkSpring Boot to Quarkus: A real app migration experience | DevNation Tech Talk
Spring Boot to Quarkus: A real app migration experience | DevNation Tech TalkRed Hat Developers
 
Developing Faster with Swagger
Developing Faster with SwaggerDeveloping Faster with Swagger
Developing Faster with SwaggerTony Tam
 
Rundeck's History and Future
Rundeck's History and FutureRundeck's History and Future
Rundeck's History and Futuredev2ops
 
Practical REPL-driven Development with Clojure
Practical REPL-driven Development with ClojurePractical REPL-driven Development with Clojure
Practical REPL-driven Development with ClojureKent Ohashi
 
Modern UI Development With Node.js
Modern UI Development With Node.jsModern UI Development With Node.js
Modern UI Development With Node.jsRyan Anklam
 
Drone CI/CD 自動化測試及部署
Drone CI/CD 自動化測試及部署Drone CI/CD 自動化測試及部署
Drone CI/CD 自動化測試及部署Bo-Yi Wu
 
Locking the Throneroom 2.0
Locking the Throneroom 2.0Locking the Throneroom 2.0
Locking the Throneroom 2.0Mario Heiderich
 
Inter-Process Communication in Microservices using gRPC
Inter-Process Communication in Microservices using gRPCInter-Process Communication in Microservices using gRPC
Inter-Process Communication in Microservices using gRPCShiju Varghese
 
Securing your AngularJS Application
Securing your AngularJS ApplicationSecuring your AngularJS Application
Securing your AngularJS ApplicationPhilippe De Ryck
 
2023 COSCUP - Whats new in PostgreSQL 16
2023 COSCUP - Whats new in PostgreSQL 162023 COSCUP - Whats new in PostgreSQL 16
2023 COSCUP - Whats new in PostgreSQL 16José Lin
 
Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Hitesh-Java
 

What's hot (20)

Introduction to Maven
Introduction to MavenIntroduction to Maven
Introduction to Maven
 
Angular
AngularAngular
Angular
 
Node js overview
Node js overviewNode js overview
Node js overview
 
Spring Boot to Quarkus: A real app migration experience | DevNation Tech Talk
Spring Boot to Quarkus: A real app migration experience | DevNation Tech TalkSpring Boot to Quarkus: A real app migration experience | DevNation Tech Talk
Spring Boot to Quarkus: A real app migration experience | DevNation Tech Talk
 
Developing Faster with Swagger
Developing Faster with SwaggerDeveloping Faster with Swagger
Developing Faster with Swagger
 
Rundeck's History and Future
Rundeck's History and FutureRundeck's History and Future
Rundeck's History and Future
 
Redis Lua Scripts
Redis Lua ScriptsRedis Lua Scripts
Redis Lua Scripts
 
gRPC in Go
gRPC in GogRPC in Go
gRPC in Go
 
Hands-on Helm
Hands-on Helm Hands-on Helm
Hands-on Helm
 
Practical REPL-driven Development with Clojure
Practical REPL-driven Development with ClojurePractical REPL-driven Development with Clojure
Practical REPL-driven Development with Clojure
 
Modern UI Development With Node.js
Modern UI Development With Node.jsModern UI Development With Node.js
Modern UI Development With Node.js
 
Drone CI/CD 自動化測試及部署
Drone CI/CD 自動化測試及部署Drone CI/CD 自動化測試及部署
Drone CI/CD 自動化測試及部署
 
Locking the Throneroom 2.0
Locking the Throneroom 2.0Locking the Throneroom 2.0
Locking the Throneroom 2.0
 
Inter-Process Communication in Microservices using gRPC
Inter-Process Communication in Microservices using gRPCInter-Process Communication in Microservices using gRPC
Inter-Process Communication in Microservices using gRPC
 
Securing your AngularJS Application
Securing your AngularJS ApplicationSecuring your AngularJS Application
Securing your AngularJS Application
 
Ansible
AnsibleAnsible
Ansible
 
Maven Nexus
Maven NexusMaven Nexus
Maven Nexus
 
2023 COSCUP - Whats new in PostgreSQL 16
2023 COSCUP - Whats new in PostgreSQL 162023 COSCUP - Whats new in PostgreSQL 16
2023 COSCUP - Whats new in PostgreSQL 16
 
Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans
 
Demystifying Prototypes
Demystifying PrototypesDemystifying Prototypes
Demystifying Prototypes
 

Similar to Asynchronous programming patterns in Perl

Playing With Fire - An Introduction to Node.js
Playing With Fire - An Introduction to Node.jsPlaying With Fire - An Introduction to Node.js
Playing With Fire - An Introduction to Node.jsMike Hagedorn
 
Socket applications
Socket applicationsSocket applications
Socket applicationsJoão Moura
 
Complex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxComplex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxbobmcwhirter
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpmsom_nangia
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpmwilburlo
 
Mojolicious - A new hope
Mojolicious - A new hopeMojolicious - A new hope
Mojolicious - A new hopeMarcus Ramberg
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryTatsuhiko Miyagawa
 
Rails Presentation (Anton Dmitriyev)
Rails Presentation (Anton Dmitriyev)Rails Presentation (Anton Dmitriyev)
Rails Presentation (Anton Dmitriyev)True-Vision
 
TPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxTPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxJirat Kijlerdpornpailoj
 
Using Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyUsing Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyLaunchAny
 
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019DevClub_lv
 
Introduction to reactive programming & ReactiveCocoa
Introduction to reactive programming & ReactiveCocoaIntroduction to reactive programming & ReactiveCocoa
Introduction to reactive programming & ReactiveCocoaFlorent Pillet
 
Building Isomorphic Apps (JSConf.Asia 2014)
Building Isomorphic Apps (JSConf.Asia 2014)Building Isomorphic Apps (JSConf.Asia 2014)
Building Isomorphic Apps (JSConf.Asia 2014)Spike Brehm
 
Going realtime with Socket.IO
Going realtime with Socket.IOGoing realtime with Socket.IO
Going realtime with Socket.IOChristian Joudrey
 

Similar to Asynchronous programming patterns in Perl (20)

Playing With Fire - An Introduction to Node.js
Playing With Fire - An Introduction to Node.jsPlaying With Fire - An Introduction to Node.js
Playing With Fire - An Introduction to Node.js
 
Socket applications
Socket applicationsSocket applications
Socket applications
 
ReactPHP
ReactPHPReactPHP
ReactPHP
 
Complex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxComplex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBox
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpm
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpm
 
Mojolicious - A new hope
Mojolicious - A new hopeMojolicious - A new hope
Mojolicious - A new hope
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Rails Presentation (Anton Dmitriyev)
Rails Presentation (Anton Dmitriyev)Rails Presentation (Anton Dmitriyev)
Rails Presentation (Anton Dmitriyev)
 
Plack - LPW 2009
Plack - LPW 2009Plack - LPW 2009
Plack - LPW 2009
 
TPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxTPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and Flux
 
PSGI/Plack OSDC.TW
PSGI/Plack OSDC.TWPSGI/Plack OSDC.TW
PSGI/Plack OSDC.TW
 
Intro to PSGI and Plack
Intro to PSGI and PlackIntro to PSGI and Plack
Intro to PSGI and Plack
 
Using Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyUsing Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in Ruby
 
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
 
Introduction to reactive programming & ReactiveCocoa
Introduction to reactive programming & ReactiveCocoaIntroduction to reactive programming & ReactiveCocoa
Introduction to reactive programming & ReactiveCocoa
 
Dancing with websocket
Dancing with websocketDancing with websocket
Dancing with websocket
 
Building Isomorphic Apps (JSConf.Asia 2014)
Building Isomorphic Apps (JSConf.Asia 2014)Building Isomorphic Apps (JSConf.Asia 2014)
Building Isomorphic Apps (JSConf.Asia 2014)
 
Going realtime with Socket.IO
Going realtime with Socket.IOGoing realtime with Socket.IO
Going realtime with Socket.IO
 

Recently uploaded

Tungsten Webinar: v6 & v7 Release Recap, and Beyond
Tungsten Webinar: v6 & v7 Release Recap, and BeyondTungsten Webinar: v6 & v7 Release Recap, and Beyond
Tungsten Webinar: v6 & v7 Release Recap, and BeyondContinuent
 
Generalities about NFT , as a new technology
Generalities about NFT , as a new technologyGeneralities about NFT , as a new technology
Generalities about NFT , as a new technologysoufianbouktaib1
 
如何办理朴茨茅斯大学毕业证书学位证书成绩单?
如何办理朴茨茅斯大学毕业证书学位证书成绩单?如何办理朴茨茅斯大学毕业证书学位证书成绩单?
如何办理朴茨茅斯大学毕业证书学位证书成绩单?krc0yvm5
 
Mary Meeker Internet Trends Report for 2019
Mary Meeker Internet Trends Report for 2019Mary Meeker Internet Trends Report for 2019
Mary Meeker Internet Trends Report for 2019Eric Johnson
 
Benefits of Fiber Internet vs. Traditional Internet.pptx
Benefits of Fiber Internet vs. Traditional Internet.pptxBenefits of Fiber Internet vs. Traditional Internet.pptx
Benefits of Fiber Internet vs. Traditional Internet.pptxlibertyuae uae
 
SQL Server on Azure VM datasheet.dsadaspptx
SQL Server on Azure VM datasheet.dsadaspptxSQL Server on Azure VM datasheet.dsadaspptx
SQL Server on Azure VM datasheet.dsadaspptxJustineGarcia32
 
overview of Virtualization, concept of Virtualization
overview of Virtualization, concept of Virtualizationoverview of Virtualization, concept of Virtualization
overview of Virtualization, concept of VirtualizationRajan yadav
 
APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85
APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85
APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85APNIC
 
Google-Next-Madrid-BBVA-Research inv.pdf
Google-Next-Madrid-BBVA-Research inv.pdfGoogle-Next-Madrid-BBVA-Research inv.pdf
Google-Next-Madrid-BBVA-Research inv.pdfMaria Adalfio
 
Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...
Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...
Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...hasimatwork
 

Recently uploaded (10)

Tungsten Webinar: v6 & v7 Release Recap, and Beyond
Tungsten Webinar: v6 & v7 Release Recap, and BeyondTungsten Webinar: v6 & v7 Release Recap, and Beyond
Tungsten Webinar: v6 & v7 Release Recap, and Beyond
 
Generalities about NFT , as a new technology
Generalities about NFT , as a new technologyGeneralities about NFT , as a new technology
Generalities about NFT , as a new technology
 
如何办理朴茨茅斯大学毕业证书学位证书成绩单?
如何办理朴茨茅斯大学毕业证书学位证书成绩单?如何办理朴茨茅斯大学毕业证书学位证书成绩单?
如何办理朴茨茅斯大学毕业证书学位证书成绩单?
 
Mary Meeker Internet Trends Report for 2019
Mary Meeker Internet Trends Report for 2019Mary Meeker Internet Trends Report for 2019
Mary Meeker Internet Trends Report for 2019
 
Benefits of Fiber Internet vs. Traditional Internet.pptx
Benefits of Fiber Internet vs. Traditional Internet.pptxBenefits of Fiber Internet vs. Traditional Internet.pptx
Benefits of Fiber Internet vs. Traditional Internet.pptx
 
SQL Server on Azure VM datasheet.dsadaspptx
SQL Server on Azure VM datasheet.dsadaspptxSQL Server on Azure VM datasheet.dsadaspptx
SQL Server on Azure VM datasheet.dsadaspptx
 
overview of Virtualization, concept of Virtualization
overview of Virtualization, concept of Virtualizationoverview of Virtualization, concept of Virtualization
overview of Virtualization, concept of Virtualization
 
APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85
APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85
APNIC Update and RIR Policies for ccTLDs, presented at APTLD 85
 
Google-Next-Madrid-BBVA-Research inv.pdf
Google-Next-Madrid-BBVA-Research inv.pdfGoogle-Next-Madrid-BBVA-Research inv.pdf
Google-Next-Madrid-BBVA-Research inv.pdf
 
Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...
Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...
Section 3 - Technical Sales Foundations for IBM QRadar for Cloud (QRoC)V1 P10...
 

Asynchronous programming patterns in Perl

  • 1. ASYNCHRONOUS PROGRAMMING! WITH! MOJO::IOLOOP::DELAY Everything you wished your dad had told you about event loops and asynchronous programming
  • 2. THE PLAN Blocking vs non-blocking! Microservices! Event loops (!)! Event-driven code (")! Other options! Mojo::IOLoop::Delay I promise these are the only emoji characters I’ll use the entire presentation.
  • 3. Take a break LET’S ORDER BREAKFAST!! pancakes! soft-boiled egg! orange juice Are you hungry? I’m hungry.
  • 4. BLOCKING CHEF mix pancakes heat skillet cook pancakes boil water cook eggs cut juice * Here’s blocking chef: he likes to look busy, so he’s always doing something. He’s going to do everything. * And what’s the problem here? It takes too long. What happens? * Blocking chef gets fired. Har.
  • 5. NON-BLOCKING CHEF heat skillet cook mix pancakes pancakes boil water cook eggs cut juice Non-blocking chef is smarter and better looking. He can tell the difference between things he has to wait for, and things he can start and then be notified when they’re ready. ! NB chef turns on the skillet and puts the water on to boil. He has alarms that tell him when the skillet is hot, when the water has boiled, and when the eggs are done.
  • 6. NUMBERS EVERYONE SHOULD KNOW—JEFF DEAN L1 cache reference 0.5 ns! Branch mispredict 5 ns! L2 cache reference 7 ns! Mutex lock/unlock 100 ns! Main memory reference 100 ns! Compress 1K bytes with Zippy 10,000 ns! Send 2K bytes over 1 Gbps network 20,000 ns Read 1 MB sequentially from memory 250,000 ns! Round trip within same datacenter 500,000 ns! Disk seek 10,000,000 ns Read 1 MB sequentially from network 10,000,000 ns Read 1 MB sequentially from disk 30,000,000 ns Send packet CA→Netherlands→CA 150,000,000 ns Why is this so bad? In human time, 30,000,000 ns doesn’t take long, but in computer time, it’s an eternity. While the network fetch happened, I could have been doing millions of cycles of real work. Blocking IO operations are terrible.
  • 7. MOJOLICIOUS::LITE use Mojolicious::Lite; ! get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; ! app->start; sets up a “route” (url handler) for GET /foo * Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.
  • 8. MOJOLICIOUS::LITE use Mojolicious::Lite; ! get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; ! app->start; sets up a “route” (url handler) for GET /foo start the daemon and run the event loop * Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.
  • 9. MOJOLICIOUS::LITE use Mojolicious::Lite; ! get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; ! app->start; sets up a “route” (url handler) for GET /foo start the daemon and run the event loop $c = “controller” (MVC pattern) * Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.
  • 10. MOJOLICIOUS::LITE use Mojolicious::Lite; ! get '/foo' => sub { sets up a “route” (url handler) for GET /foo start the daemon and run the event loop $c = “controller” (MVC pattern) invoke render() my $c = shift; $c->render( json => { foo => “bar” }); }; ! app->start; * Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.
  • 11. MOJOLICIOUS::LITE use Mojolicious::Lite; ! get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; ! app->start; sets up a “route” (url handler) for GET /foo start the daemon and run the event loop $c = “controller” (MVC pattern) invoke render() } set content-type to application/json and serialize the hashref * Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.
  • 12. MOJOLICIOUS::LITE command-line GET! start daemon! start pre-forking daemon! list routes! run tests $ ./my-app get /foo {"foo":"bar"} $ ./my-app daemon Server available at http://127.0.0.1:3000. $ hypnotoad ./my-app $ ./my-app routes $ ./my-app test Some other Mojolicious goodies.
  • 13. MICROSERVICE ARCHITECTURE Microservices: small HTTP (or other TCP) services! do one thing, do it well! small is beautiful! composition! text (JSON) is the universal interface! Speak HTTP everywhere * Microservices are the network extension of the Unix philosophy. * Composition: we can compose powerful functions from simpler functions. * We’re going to do a little of that tonight.
  • 14. MICROSERVICE EXAMPLE 1. get our source IP address! 2. using the IP address, get our geolocation! 3. using the geolocation:! • get the weather! • get the moon phase! • get the current air quality index (AQI) This is what we’re going to eventually build.
  • 15. MICROSERVICE: MY IP ADDRESS GET http://demo:8082/my-ip {"ip":"71.199.21.36"} I wrote this simple service to return your source IP, or the IP you’re connecting from.
  • 16. MICROSERVICE: GEOLOCATION GET http://ip-api.com/json/71.199.21.36 { "query" : "71.199.21.36", "country" : "United States", "timezone" : "America/Denver", "region" : "UT", "city" : "American Fork", "zip" : “84003", "lat" : 40.393, "lon" : -111.7838 } This service takes an IP address and returns the location where that IP address originates.
  • 17. MICROSERVICE: MOON PHASE GET http://demo:8080/pom { "text" : "94.0%, waning gibbous", "phase" : "0.5787", "illumination" : "0.9401", "age" : "17.0893" } I wrote this service using the venerable Astro::MoonPhase.
  • 18. MICROSERVICE: WEATHER GET http://demo:8084/weather/40.2960,-111.6946 { "windBearing" : 85, "icon" : "clear-day", "pressure" : 1016.52, "windSpeed" : 3.73, "cloudCover" : 0, "summary" : "Clear", "dewPoint" : 35.35, "humidity" : 0.65, "temperature" : 46.41, } This service is the excellent forecast.io; there’s a ton more information returned here, but the essence of what we’ll be using is here.
  • 19. MICROSERVICE: AIR QUALITY GET http://demo:8083/aqi/40.296/-111.6946 { "pollutant" : "small particulate matter", "index" : 22 } This service gives us the air quality index on a scale of 0 to 500 I believe.
  • 20. MICROSERVICE: SLOW GET http://demo:8081/slow/3 {"waited":3} I wrote this service to show exaggerated latency
  • 21. A BLOCKING MOJO APP use Mojolicious::Lite; ! get '/weather' => sub { my $c = shift; ! my $ua = $c->ua; ! my $tx1 = $ua->get('http://demo:8080/pom'); my $pom = $tx1->res->json('/text'); ! my $tx2 = $ua->get('http://demo:8081/slow/2'); my $slow = $tx2->res->json('/waited'); ! my $tx3 = $ua->get('http://demo:8082/my-ip'); my $ip = $tx3->res->json('/ip'); ! $c->render(json => { pom => $pom, waited => $slow, ip => $ip }); }; ! app->start; * here’s our app * we make a call to POM * then we call slow, etc.
  • 22. HOW DOES IT PERFORM? $ wrk -c 1 -d 10 -t 1 http://localhost:3000/weather Running 10s test @ http://localhost:3000/weather 1 threads and 1 connections Thread Stats Avg Stdev Max +/- Stdev Latency 2.14s 0.00us 2.14s 100.00% Req/Sec 0.00 0.00 0.00 100.00% 4 requests in 10.01s, 0.88KB read Socket errors: connect 0, read 0, write 0, timeout 1 Requests/sec: 0.40 Transfer/sec: 89.94B poorly: 0.4 req/sec You might see that I’ve limited the connections down to one; if I went higher than this, wrk’s stats messed up and I couldn’t get any requests per second. Even higher, this application blocks and can only handle one connection at a time anyway.
  • 23. Got any ideas? SOLUTIONS? hypnotoad! still doesn’t scale well… Hypnotoad is like Apache: fork off some daemons and hope there are enough of them to handle the load. If we pass our max clients, the next client to connect is going to have to wait.
  • 24. ASYNCHRONOUS EXAMPLE: SIGNAL HANDLERS $SIG{INT} = sub { say "Got an INT signal!"; exit }; ! while(1) { sleep } What is an example of a common asynchronous thing we see when we write programs?
  • 25. ASYNCHRONOUS EXAMPLE: JAVASCRIPT CALLBACKS <script type=“text/javascript"> ! $(document).ready(function() { jQuery.ajax(“http://www.perl.org”, { success: function(data) { alert("loaded!") } }) }) ! </script> If you’ve used jQuery, you’ve almost certainly written a callback. The browser has an event loop that is continually checking for UI events such as mouse clicks, DOM events, or in this case, an ajax load to complete.
  • 26. ASYNCHRONOUS EXAMPLE: JAVASCRIPT CALLBACKS <script type=“text/javascript"> ! $(document).ready(function() { jQuery.ajax(“http://www.perl.org”, { success: function(data) { alert("loaded!") } }) }) ! </script> If you’ve used jQuery, you’ve almost certainly written a callback. The browser has an event loop that is continually checking for UI events such as mouse clicks, DOM events, or in this case, an ajax load to complete.
  • 27. a- “not” + synchronus “together-time” EVENT LOOPS! not parallel! not threaded! event-driven! non-blocking Speaking of event loops… we’re talking about: not parallel programming: multiple cores on one problem not threaded programming: multiple threads on one problem event-driven: our code runs when events occur non-blocking: we don’t have to wait for any I/O
  • 28. EVENT LOOPS ARE MYSTERIOUS Event! EV! POE! IO::Async! Mojolicious use Event 'loop'; my $loop = loop(); use EV; EV::run; use POE; POE::Kernel->run(); use IO::Async::Loop; my $loop = IO::Async::Loop->new; $loop->run; use Mojolicious::Lite app->start; Here are some event loops I’ve used over the years, and I never really knew what was going on under the hood. It turns out, it’s pretty simple.
  • 29. HOW EVENT LOOPS WORK* my @events = (); my %watchers = ( network => sub { say "Got network data: " . shift } ); ! while(sleep 1) { if (my $evt = shift @events) { push @events, check_events(); my ($event, $data) = @$evt; $watchers{$event}->($data); if (my $evt = shift @events) { } ! ! my ($event, $data) = @$evt; $watchers{$event}->($data); check_events(); } } ! sub check_events { if (my $data = network_event() and $watchers{network}) { return [network => $data]; } ! if (my $data = timer_event() and $watchers{timer}) { return [timer => $data]; } ! return (); } * “A little inaccuracy sometimes saves tons of explanation.”—H.H. Munroe We loop forever waiting for events to happen. When an event happens, we run some code that was waiting for it and then we loop some more. ! What kinds of events can we watch for? Timers, signals, disk IO, network IO, even custom events that we create ourselves. ! Event loops allow us to get lots of things done while we otherwise might be waiting for slow, blocking things. For example, it only takes a few microseconds to build a web request and put it on the wire, but it takes many milliseconds, sometimes seconds, for the response to come back. That's an eternity for the computer. Why don't we instead fan out as many requests as we have resources for and then handle the responses when they come back? Disk reads are the same: they're often worse than network requests—waiting for I/O in general means you're not getting your money's worth from your CPU. Remember blocking chef.
  • 30. get '/weather' => sub { my $c = shift; ! my $tx1 = $c->ua->get(‘http://demo:8080/pom'); my $pom = $tx1->res->json('/text'); ! my $tx2 = $c->ua->get(‘http://demo:8081/slow/2'); my $slow = $tx2->res->json('/waited'); ! }; get '/weather' => sub { my $c = shift; $c->render_later; ! $c->ua->get(‘http://demo:8080/pom' => sub { my $tx = pop; my $pom = $tx->res->json('/text'); ! $c->ua->get(‘http://demo:8081/slow/2' => sub { my $slow = pop->res->json('/waited'); ... }); }); }; blocking non-blocking Here’s our same application but converted to non-blocking mode using callbacks. ! In event loops, functions no longer return anything, or at least anything useful. All of the useful stuff is passed to the callback instead. ! In event loops, when we want to force an ordering, we have to put the dependent code inside the callback also. ! Note that event-driven code doesn’t run faster, it just doesn’t block. An individual request still takes the same amount of time, but the event loop isn’t blocked while waiting for slow requests, and can handle many more at the same time.
  • 31. HOW DOES IT PERFORM? $ wrk -c 10000 -d 10 -t 100 http://localhost:3000/weather Running 10s test @ http://localhost:3000/weather 100 threads and 10000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 3.31s 1.87s 7.88s 51.25% Req/Sec 28.50 17.21 53.00 75.00% 1314 requests in 10.01s, 258.28KB read Socket errors: connect 0, read 12, write 0, timeout 47624 Requests/sec: 131.31 Transfer/sec: 25.81KB better: 131 req/sec! 300x speedup! * note: each individual request still takes 2s, but we can now handle hundreds or thousands of these simultaneously because we’re not blocking on the slow calls.
  • 32. DRAWBACKS Continuation-passing style! event-loop concurrency! flow control, error handling * CPS is hard to read and work with * We achieve concurrency only in the main event loop, not within the handler itself; e.g., even if we had things that *could* happen at the same time, we can’t do that. * Control over flow
  • 33. get '/weather' => sub { my $c = shift; $c->render_later; ! my $ua = $c->ua; ! $ua->get('http://demo:8080/pom' => sub { my $tx = pop; my $pom = $tx->res->json('/text'); ! $ua->get('http://demo:8081/slow/2' => sub { my $tx = pop; my $slow = $tx->res->json(‘/waited'); ! $ua->get('http://demo:8082/my-ip' => sub { my $tx = pop; my $ip = $tx->res->json(‘/ip'); ! $c->render(json => { pom => $pom, waited => $slow, ip => $ip }, status => 200); }); }); }); }; This is a picture of the famous pyramid of death aka callback-hell.
  • 34. Thanks to tempire for the meme
  • 35. What now? OTHER SOLUTIONS AnyEvent! Promises! Mojo::IOLoop::Delay
  • 36. ANYEVENT PRIMER ad-hoc flow control! powerful primitives CondVars give us ad hoc flow control in any event loop. They’re powerful primitives and you can build your own event system on top of them.
  • 37. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $ fetch-urls (walk through as if you were the Perl interpreter)
  • 38. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $ fetch-urls (walk through as if you were the Perl interpreter)
  • 39. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 0 cb: undef $ fetch-urls (walk through as if you were the Perl interpreter)
  • 40. use AnyEvent; use EV; ! my $cv = AE::cv; ! !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 0 cb: undef $cv->cb(sub { say “all urls done” }); $ fetch-urls (walk through as if you were the Perl interpreter)
  • 41. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 0 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 42. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 0 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 43. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 1 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 44. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 1 cb: sub { … } $ua->get($url1 $ fetch-urls (walk through as if you were the Perl interpreter)
  • 45. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 1 cb: sub { … } $ua->get($url1 $ fetch-urls $url1 request… (walk through as if you were the Perl interpreter)
  • 46. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 1 cb: sub { … } $ua->get($url1 $ fetch-urls (walk through as if you were the Perl interpreter)
  • 47. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 1 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 48. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 49. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 50. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls $url2 request… (walk through as if you were the Perl interpreter)
  • 51. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 52. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls (walk through as if you were the Perl interpreter)
  • 53. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls running... (walk through as if you were the Perl interpreter)
  • 54. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } $ fetch-urls running... (walk through as if you were the Perl interpreter)
  • 55. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv wait… counter: 2 cb: sub { … } $ fetch-urls running... (walk through as if you were the Perl interpreter)
  • 56. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv wait… counter: 2 cb: sub { … } receive $url2 response $ fetch-urls running... (walk through as if you were the Perl interpreter)
  • 57. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } } wait… receive $url2 response run get() callback $ fetch-urls running... url2 done (walk through as if you were the Perl interpreter)
  • 58. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run $cv counter: 2 cb: sub { … } } wait… receive $url2 response run get() callback $cv->end $ fetch-urls running... url2 done (walk through as if you were the Perl interpreter)
  • 59. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run wait… receive $url2 response run get() callback $cv counter: 1 $cv->end cb: sub { … } $ fetch-urls running... url2 done (walk through as if you were the Perl interpreter)
  • 60. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run wait… receive $url2 response run get() callback $cv->end wait… $cv counter: 1 cb: sub { … } $ fetch-urls running... url2 done (walk through as if you were the Perl interpreter)
  • 61. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run wait… receive $url2 response run get() callback $cv->end wait… receive $url1 response $cv counter: 1 cb: sub { … } $ fetch-urls running... url2 done (walk through as if you were the Perl interpreter)
  • 62. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run sub { say “url1 done”; $cv->end } wait… receive $url2 response run get() callback $cv->end wait… receive $url1 response run get() callback $cv counter: 1 cb: sub { … } $ fetch-urls running... url2 done url1 done (walk through as if you were the Perl interpreter)
  • 63. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run sub { say “url1 done”; $cv->end } wait… receive $url2 response run get() callback $cv->end wait… receive $url1 response run get() callback $cv->end $cv counter: 1 cb: sub { … } $ fetch-urls running... url2 done url1 done (walk through as if you were the Perl interpreter)
  • 64. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run sub { say “url1 done”; $cv->end } wait… receive $url2 response run get() callback $cv->end wait… receive $url1 response run get() callback $cv counter: 0 $cv->end cb: sub { … } $ fetch-urls running... url2 done url1 done (walk through as if you were the Perl interpreter)
  • 65. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(sub { say “all urls done” }); !! $cv->begin; $ua->get($url1 => }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run sub { say “url1 done”; $cv->end } wait… receive $url2 response run get() callback $cv->end wait… receive $url1 response run get() callback $cv counter: 0 $cv->end cb: sub { … } $ fetch-urls running... url2 done url1 done (walk through as if you were the Perl interpreter)
  • 66. use AnyEvent; use EV; ! my $cv = AE::cv; ! $cv->cb(}); !! sub { say “all urls done” } $cv->begin; $ua->get($url1 => }); ! $cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); ! say “running...” EV::run sub { say “url1 done”; $cv->end } wait… receive $url2 response run get() callback $cv->end wait… receive $url1 response run get() callback $cv counter: 0 $cv->end cb: sub { … } $ fetch-urls $ fetch-urls running... running... url2 done url1 done all urls done (walk through as if you were the Perl interpreter)
  • 67. use Mojolicious::Lite; use AnyEvent; ! get '/weather' => sub { my $c = shift; $c->render_later; ! my %res = (); ! my $cv = AnyEvent->condvar; $cv->cb(sub { $c->render( json => %res, status => 200 ) } ); ! $cv->begin; $c->ua->get( 'http://demo:8080/pom' => sub { $res{pom} = pop->res->json('/text'); $cv->end } ); ! $cv->begin; $c->ua->get( 'http://demo:8081/slow/2' => sub { $res{slow} = pop->res->json('/waited'); $cv->end } ); ! $cv->begin; $c->ua->get( 'http://demo:8082/my-ip' => sub { $res{ip} = pop->res->json('/ip'); $cv->end } ); }; ! app->start; ANYEVENT * we’ll set up a hash to catch responses, make condvar, etc. * This is raw AnyEvent programming * We could add lots of abstractions here to make this cleaner * We avoid CPS, but we have global %res * Performs well
  • 68. ANYEVENT REVIEW primitives! flow! flexible! framework agnostic * AnyEvent provides low-level event primitives with condvars * flow is hard to see * 100% flexible to any kind of pattern, but you have to DIY * powerful and fast
  • 69. use Mojolicious::Lite; use Promises backend => ['EV'], qw/deferred collect/; ! helper get_url => sub { my $c = shift; my $url = shift; my $deferred = deferred; $c->ua->get($url => sub { $deferred->resolve( pop->res->json ) }); $deferred->promise; }; ! get '/weather' => sub { my $c = shift; ! collect( $c->get_url('http://demo:8080/pom'), $c->get_url('http://demo:8081/slow/2'), $c->get_url('http://demo:8082/my-ip'), )->then( sub { my ( $pom, $slow, $ip ) = map { $_->[0] } @_; $c->render(json => {pom => $pom->{text}, ip => $ip->{ip}, slow => $slow->{waited}}); }, sub { warn @_ } ); }; ! app->start; PROMISES * The Promises module is nice if you’re coming from a JavaScript background * Better built-in error handling than AE * Better flow than AE out of the box * Performs well * Requires named functions to avoid lots of boilerplate
  • 70. PROMISES REVIEW additional abstractions! promises pattern! framework agnostic * Additional abstractions: as you move toward functional programming, Promises gets complex, but not as bad as callbacks * The promises pattern is not always intuitive * Hard to change from blocking to promises (my experience) * Lots to love, actually—framework agnostic
  • 71. And now the moment you’ve all been waiting for is here… MOJO::! IOLOOP::DELAY Mojo::IOLoop::Delay combines the flexibility of condvars and the ease of use of Promises.
  • 72. CALLBACK PROBLEMS callback hell! continuation-passing style! flow control
  • 73. MOJO::IOLOOP::DELAY++ flat call chains! powerful primitives like AnyEvent! clean abstractions like Promises! mix-n-match sequential/concurrent patterns * Ideal choice for Mojolicious applications
  • 74. BOILERPLATE my $delay = Mojo::IOLoop::Delay->new; $delay->steps( sub { my $d = shift; (non-blocking call) }, sub { my $d = shift; (non-blocking results) }, ); * we create the $delay object first * we call its steps() method * steps always happen sequentially
  • 75. STEPS Steps are just subroutines you define! Each step receives the $delay object and all of the arguments passed to it from the previous step! The next step will not run until:! the current step has created one or more step callbacks! each step callback has been invoked
  • 76. CALLBACK REDUX get '/weather' => sub { my $c = shift; $c->render_later; ! $c->ua->get(‘http://demo:8080/pom' => sub { my $pom = $tx->res->json('/text'); ! $c->ua->get(‘http://demo:8081/slow/2' => sub { my ($ua, $tx) = @_; my $slow = $tx->res->json('/waited'); ... }); }); }; my ($ua, $tx) = @_; * Remember that the get() callback receives the useragent object and the transaction object.
  • 77. STEP PATTERNS $delay->steps( sub { my $d = shift; ! $c->ua->get($url => sub { $end->(@_, ‘foo’) }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, ); my $end = $d->begin(0); make a step callback * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step
  • 78. STEP PATTERNS $delay->steps( sub { invoke the step callback my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, ); $end->(@_, ‘foo’) * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step
  • 79. STEP PATTERNS $delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_, foo’) }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, ); @_, ‘foo’ step callback arguments! pass to the next step * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step
  • 80. STEP PATTERNS $delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_, ‘foo’) }); }, sub { my $d = shift; ! ... }, ); my ($ua, $tx, $foo) = @_; step callback arguments! pass to the next step * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step
  • 81. COMPARE TO CONDVARS AnyEvent condition variables have begin() and end() methods! Mojo::IOLoop::Delay objects have only a begin() method which returns a subroutine reference. It is roughly equivalent to this using AnyEvent condvars: sub begin { my $cv = shift; $cv->{counter}++; return sub { $cv->end }; } * If we were to implement Mojo::IOLoop::Delay using AnyEvent primitives, we might come up with something like this.
  • 82. STEP PATTERNS $delay->steps( sub { my $d = shift; ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, ); my $end = $d->begin(1); make a step callback * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * the (1) argument tells begin() how many arguments to shift off * this makes our second step cleaner * you can see our second step now only receives the $tx, not the $ua
  • 83. STEP PATTERNS $delay->steps( sub { invoke the step callback my $d = shift; my $end = $d->begin(1); ! $c->ua->get($url => sub { }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, ); $end->(@_) * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * the (1) argument tells begin() how many arguments to shift off * this makes our second step cleaner * you can see our second step now only receives the $tx, not the $ua
  • 84. STEP PATTERNS $delay->steps( sub { my $d = shift; my $end = $d->begin(1); ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; ! ... }, ); my ($tx) = @_; * we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * the (1) argument tells begin() how many arguments to shift off * this makes our second step cleaner * you can see our second step now only receives the $tx, not the $ua
  • 85. STEP PATTERNS $delay->steps( sub { my $d = shift; ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, ); my $end = $d->begin; make a step callback * it turns out that “1” is the default for begin(); we can leave it off * Note that Mojo::IOLoop::Delay’s begin() function is richer than AnyEvent’s—it not only increments an internal counter, but it also returns the very callback to invoke to decrement the counter.
  • 86. STEP PATTERNS $delay->steps( sub { invoke the step callback my $d = shift; my $end = $d->begin; ! $c->ua->get($url => sub { }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, ); $end->(@_) * it turns out that “1” is the default for begin(); we can leave it off * Note that Mojo::IOLoop::Delay’s begin() function is richer than AnyEvent’s—it not only increments an internal counter, but it also returns the very callback to invoke to decrement the counter.
  • 87. STEP PATTERNS $d->begin creates a step callback $delay->steps( sub { my $d = shift; $c->ua->get($url => begin); }, sub { my $d = shift; my $tx = shift; ! ... }, ); $d->begin The step callback is invoked when the! user-agent transaction response happens. * If all you’re doing in your non-blocking callback is invoking the step callback, you can save yourself some time and boilerplate * This is a common pattern
  • 88. GETTING TO THE NEXT STEP steps that make asynchronous calls must create a step callback before the asynchronous call, then invoke it after the call completes (i.e., in the callback): sub { my $d = shift; my $end = $d->begin(1); $c->ua->get($url => sub { $end->(@_) }); } sub { my $d = shift; my $end = $d->begin; $c->ua->get($url => $end); } or save yourself some typing and create the step callback in the asynchronous call: sub { my $d = shift; $c->ua->get($url => $d->begin); } * these are functionally equivalent * use the top left version if you need to modify the arguments before they’re passed to the next step * use the bottom version for clarity * remember: begin() creates a step callback!
  • 89. GETTING TO THE NEXT STEP steps that do not make asynchronous calls may use pass() sub { my $d = shift; $d->pass(@stuff); } sub { my $d = shift; 0)->(@stuff); } $d->begin(0) * This lower one is interesting: we make a step callback here (click), then we immediately invoke it * If you’re not familiar with functional programming in Perl, this can be a little mind bending.
  • 90. GETTING TO THE NEXT STEP steps that do not make asynchronous calls may use pass() sub { my $d = shift; $d->pass(@stuff); } sub { my $d = shift; $d->begin(0)->(@stuff); } * This lower one is interesting: we make a step callback here (click), then we immediately invoke it * If you’re not familiar with functional programming in Perl, this can be a little mind bending.
  • 91. QUIZ: 1 OF 4 $delay->steps( sub { my $d = shift; ! $c->ua->get($url => $d->begin); }, sub { my $d = shift; my (?) = @_; ! ... }, ); ## $tx Will the second step ever be called? What arguments will it receive?
  • 92. QUIZ: 2 OF 4 $delay->steps( sub { my $d = shift; ! $c->ua->get($url => sub { $d->begin }); }, sub { my $d = shift; my (?) = @_; ! ... }, ); Will the second step ever be called? What arguments will it receive? Not reached! The steps callback created by $d->begin isn’t invoked until the non-blocking callback runs and by then the delay has stopped. The step callback must be created before the non-blocking call is made.
  • 93. QUIZ: 3 OF 4 $delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my (?) = @_; ! ... }, ); ## ($ua, $tx) Will the second step ever be called? What arguments will it receive?
  • 94. QUIZ: 4 OF 4 $delay->steps( sub { my $d = shift; my $end = $d->begin; ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my (?) = @_; ! ... }, ); ## $tx Will the second step ever be called? What arguments will it receive?
  • 95. QUIZ: 5 OF 4 $delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_) if @_ }); }, sub { my $d = shift; my (?) = @_; ! ... }, ); Will the second step ever be called? What arguments will it receive? * Maybe, if there are arguments from the callback. * This is risky. * remember: you must invoke $d->begin or $d->pass before you leave the step
  • 96. QUIZ: EXTRA CREDIT $delay->steps( sub { my $d = shift; $d->pass(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, ); Will the second step ever be called? What arguments will it receive?
  • 97. QUIZ: EXTRA CREDIT $delay->steps( sub { my $d = shift; $d->begin(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, ); Will the second step ever be called? What arguments will it receive? * Trick question! The answer is “no” because the step callback is never invoked.
  • 98. QUIZ: EXTRA CREDIT $delay->steps( sub { my $d = shift; $d->begin->(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, ); Will the second step ever be called? What arguments will it receive?
  • 99. QUIZ: EXTRA CREDIT $delay->steps( sub { my $d = shift; $d->begin(0)->(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, ); Will the second step ever be called? What arguments will it receive?
  • 101. CONCURRENCY use Mojolicious::Lite; ! get '/weather' => sub { my $c = shift; ! Mojo::IOLoop::Delay->new->steps( sub { my $d = shift; $c->ua->get( 'http://demo:8080/pom' => $d->begin ); $c->ua->get( 'http://demo:8081/slow/2' => $d->begin ); $c->ua->get( 'http://demo:8082/my-ip' => $d->begin ); }, sub { my $d = shift; $c->render( json => { moon => shift->res->json('/text'), slow => shift->res->json('/waited'), ip => shift->res->json('/ip') }); } ); }; ! app->start; * We can make multiple step callbacks! The next step will not run until all step callbacks have been invoked. * Notice how succinct our non-blocking callbacks are. We can make multiple, non-blocking calls in the same step, and they’re all run concurrently. * The next step will receive the results in the order they were invoked. In this case, we get three transaction objects and we can just shift them off in our render()
  • 102. SERIALIZATION Mojo::IOLoop::Delay->new->steps( sub { my $d = shift; $c->ua->get( 'http://demo:8082/my-ip' => $d->begin ); }, sub { my $d = shift; my $ip = shift->res->json('/ip'); ! $c->ua->get( "http://demo:8080/pom?ip=$ip" => $d->begin ); }, sub { my $d = shift; my $pom = shift->res->json('/text'); ! $c->ua->get( 'http://demo:8081/slow/2' => $d->begin ); }, sub { my $d = shift; $c->render( json => { slow => shift->res->json('/waited') } ); } ); * Here we demonstrate how do make non-blocking calls in a particular order, and only after the previous non-blocking call has finished. * We might want to do this if we have a call to make that depends on a previous non-blocking call. This is pretty common. * You can see the chain of events, and passed arguments.
  • 103. MICROSERVICE EXAMPLE 1. get our source IP address! 2. using the IP address, get our geolocation! 3. using the geolocation:! • get the weather! • get the moon phase! • get the current air quality index (AQI)
  • 104. get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, ! sub { my $d = shift; my $ip = shift->res->json('/ip'); $c->ua->get("http://ip-api.com/json/$ip" => $d->begin); }, ! sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, ! sub { my $d = shift; ! $c->render(json => { moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') }); } ); }; * Here is the final example; we’re pulling out all the stops and making a mixture of sequential, dependent calls and concurrent, independent calls. * This isn’t bad, and I feel yields one of the clearest and least boilerplate-y approaches to event loop programming. * But what bothers me here is we have to pass our arguments from one step to another, or we have to use a variable outside of the delay object. * We can do a better.
  • 105. DELAY “STASH” VIA DATA() scratch space! keeps state * data() gives us a scratch space or “stash” attached to the delay object * it keeps state for the life of the delay object, then cleans up when the delay is finished * solves the AnyEvent problem of keeping around external data structures * solves the step() problem of having to collect and pass arguments from step to step
  • 106. Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, ! sub { my $d = shift; $c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); }, ! sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); $d->data(lat => $lat, lon = $lon); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, ! sub { my $d = shift; ! $c->render(json => $d->data); ## also includes ip, lat, lon } ); $d->data(ip => shift->res->json('/ip')); $d->data( moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') ); * here we store the results of each step in the data() method for the delay * this adds a little bulk without much benefit here * but it allows us to do other interesting things
  • 107. Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, ! sub { my $d = shift; $d->data(ip => shift->res->json('/ip')); $c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); }, ! sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); $d->data(lat => $lat, lon = $lon); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, ! sub { my $d = shift; $d->data( moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') ); ! $c->render(json => $d->data); ## also includes ip, lat, lon } ); * here we store the results of each step in the data() method for the delay * this adds a little bulk without much benefit here * but it allows us to do other interesting things
  • 108. ABSTRACTING STEPS helper req_steps => sub { my ( $c, $name, $url ) = @_; ( sub { $c->ua->get( $url => shift->begin ) }, sub { shift->data( $name => shift->res->json("/$name") )->pass } ); }; ! get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( $c->req_steps( text => 'http://demo:8080/pom' ), $c->req_steps( waited => 'http://demo:8081/slow/2' ), $c->req_steps( ip => 'http://demo:8082/my-ip' ), sub { my $d = shift; $c->render( json => $d->data ); } ); }; * here we have a helper that returns two steps * the first step makes a non-blocking call to the URL we specify * the second step stores the results in data() with the key we gave it * then in our controller, we call the helper for each URL, and in the last step we collect the results and render * this is great if your HTTP requests are simple, but we can do even better
  • 109. ABSTRACTING DELAYS Can we call a delay from within a step? * the Mojo::IOLoop is a singleton, so all events from all Delay objects are sent to the same place
  • 110. IDEAL FRAMEWORK get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( sub { get geolocation }, sub { get weather }, sub { my $d = shift; $c->render( json => shift ); } ); }; * here’s our framework we want in pseudo-code * get the geo-location * get the weather * render the response * remember: steps receive data from the previous step if it was passed via pass() or the step callback. * what does get geolocation look like?
  • 111. GEOLOCATION STEP helper 'get.geolocation' => sub { my $c = shift; ! sub { my $delay = shift; my $data = shift; my $delay_end = $delay->begin(0); ! Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, sub { my $d = shift; $d->data(ip => shift->res->json('/ip')); $c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); }, sub { my $d = shift; my $tx = shift; $d->data(lat => $tx->res->json('/lat'), lon => $tx->res->json('/lon')); $delay->data('get.geolocation' => $d->data); $delay_end->($d->data); } ); } }; * Here is our helper; remember that this helper will be running inside of a Delay object, so it needs to return a step, or sub ref. The step receives the delay object, and possibly some data from the previous step, which we can’t see here. We then run $delay->begin(0) since we may be making non-blocking calls and we want to not move on to the next step until they’re all done. * Now we create our own delay object to: * get the IP * get the geolocation based on the IP * store the IP and lat/long into the delay data() store * pass that delay data store to the outer delay data store * pass the delay data store along to the next step, which we haven’t written yet
  • 112. WEATHER STEP Mojo::IOLoop::Delay->new->steps( sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $data->{lat}, $data->{lon} ); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, sub { my $d = shift; my ($pom_tx, $aqi_tx, $weather_tx) = @_; ! $d->data(pom => $pom_tx->res->json(‘/text'), aqi => $aqi_tx->res->json(‘/index'), temp => $weather_tx->res->json('/currently/temperature'), summary => $weather_tx->res->json('/currently/summary')); $delay->data('get.weather' => $d->data); $delay_end->($d->data); } ); * this weather step follows exactly the same pattern * we create a step/subref (not shown) * in the step we create our own Delay object * the first step uses the data from the “previous” step, which was our geolocation step * the last step marshals all the responses and returns another data structure
  • 113. OUTER DELAY AND CONTROLLER get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( $c->get->geolocation, $c->get->weather, sub { my $d = shift; $c->render( json => shift ); } ); }; * Now we’re cooking with panache! * We can see the power of this pattern: * we could build libraries of these functions * each has its own function signatures: expected input and outputs, those could be checked, add error handling, etc. * The price of this simplicity was a bit of boilerplate in the helpers, but that could be easily abstracted with a plugin * tempire will get right on that
  • 115. HIRE ME! I’m looking for something great—with Perl!! Email: scott@perlcode.org! Voice/sms: +1.801.796.2484