the event-driven nature of javascript – ipc2012
TRANSCRIPT
THE EVENT-DRIVEN NATURE OF JAVASCRIPT
MARTIN SCHUHFUSS | SPOT-MEDIA AG
ÜBER MICH
/ Hamburger
/ ursprünglich PHP-Entwickler
/ Javascript-Nerd, Performance-Fetischist
/ node.js und interactive-development
/ Architekt und Entwickler bei spot-media
MARTIN SCHUHFUSS@usefulthink
UND IHR?
JAVASCRIPT ?
node.js ?
WORUM GEHT‘S?
THE EVENT-DRIVEN NATURE OF JAVASCRIPT
SERVERSIDE JS ANDNON-BLOCKING I/O
THE ASYNC NATURE OF JAVASCRIPT
JAVASCRIPT EVENTS AND THE EVENT-LOOP
zOMG, CALLBACKS EVERYWHERE
JAVASCRIPT: THE BEST PART
FIRST-CLASS FUNCTIONS/ Funktionen sind Objekte
/ …als Parameter
/ …als Rückgabewert
/ …in Variablenzuweisungen
/ Es gelten keine speziellen Regeln für Funktionen
/ Funktionen haben auch Eigenschaften und Methoden
/ z.B. fn.name oder fn.call() / fn.apply()
FIRST-CLASS FUNCTIONS// a simple functionfunction something() { console.log("something"); }
// functions assigned as valuesvar aFunction = function() { /* ... */ }, somethingElse = something;
// function returning a functionfunction getFunction() { return function(msg) { console.log(msg); }}
var fn = getFunction();fn("foo"); // "foo"getFunction()("foo"); // works the same way!
FIRST-CLASS FUNCTIONS
// functions as parametersfunction call(fn) { return fn();}
// passing an anonymous functioncall(function() { console.log("something");}); // "something"
IMMEDIATE FUNCTIONS
(function __immediatelyExecuted() { console.log("something");} ());
// in short:(function() { ... } ());
unmittelbar nach Deklaration ausgeführte Funktionen
CLOSURES
/ definieren scopes für Variablen
/ ermöglichen private Variablen
/ „einfrieren“ von Werten
/ Ermöglichen „konfigurierbare“ Funktionen
CLOSURES
var deg2rad = (function() { var RAD_PER_DEG = Math.PI/180;
return function(deg) { return deg * RAD_PER_DEG; }}());
console.log(RAD_PER_DEG); // undefineddeg2Rad(180); // 3.1415926…
Scoping: „private“ Variablen
CLOSURES
// for example to create a jQuery-plugin:(function($, window) { var somethingPrivate = null;
$.fn.extend({ plugin: function() { // here‘s the plugin-code
return this; } });} (this.jQuery, this));
ALLES ZUSAMMEN
CLOSURES
var animate = (function(hasTransitions) { if(hasTransitions) { return function(params) { // animate using css-transitions }; } else { return function(params) { // animate using javascript, or better // don‘t animate at all. };}(Modernizr.csstransitions));
„Konfigurierbare“ Funktionen
CLOSURES
for(var i=0; i<3; i++) { setTimeout(function() { console.log("i=" + i); }, 0);}
// -> i=3, i=3, i=3
there is no block-scope…
WTF?
CLOSURES
for(var i=0; i<3; i++) { (function(value) { setTimeout(function() { console.log("i=" + value); }, 0); } (i));}
Closures zum „einfrieren“ von Variablen
// -> i=0, i=1, i=2
NON-BLOCKING FUNCTIONS
ASYNC FUNCTIONS
ASYNC FUNCTIONSASYNC FUNCTIONS ARE JAVASCRIPTS
ANSWER TO position: absolute;Jed Schmidt, getting functional with (fab) – JSConf.eu 2010
NON-BLOCKING FUNCTIONS
/ keine direkten Rückgabewerte
/ „Callbacks“ zur Fortsetzung des Programmablaufs
/ Background-Tasks übernehmen die Arbeit
/ keine Wartezeit innerhalb des Programms
NON-BLOCKING FUNCTIONS
var r = new XMLHttpRequest();r.open("GET", "/foo/bar", true);
r.onreadystatechange = function () { if (r.readyState != 4 || r.status != 200) return;
console.log("Success: " + r.responseText);};
r.send();console.log("Request sent!");
doesn‘t block execution!
async „return“
BLOCKING vs. NON-BLOCKING<?php
$browser = new Buzz\Browser();$response = $browser -> get('http://google.com');
echo $response;echo 'done!';
var http = require('http');
http.get('http://google.com', function(res) { console.log(res); });console.log('on the way!');
https://github.com/kriswallsmith/Buzz http://nodejs.org/api/http.html
BLOCKING vs. NON-BLOCKING
WHERE IS THE DIFFERENCE?
CPU-Registers(~1 cycle, 0.33 ns)
L1-Cache(~3 cycles, 1 ns)
L2-Cache(~14 cycles, 4.7 ns)
http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait
RAM(~250 cycles, 83 ns)
HDD-Seek(~41,000,000 cycles, ~13.7ms)
NETWORK(~240,000,000 cycles, ~80ms)
100
101
102
103
104
105
106
107
108
CP
UC
YCLE
S
if a single Instruction would take 1 second to execute, the HTTP-request to google.com would
require us to wait for 8 years and more.
BLOCKING vs. NON-BLOCKINGWHERE IS THE DIFFERENCE?
logarithmic scale!
BLOCKING vs. NON-BLOCKING
<?php
$browser = new Buzz\Browser();$response = $browser -> get('http://google.com');
echo $response;echo 'done!';
WHERE IS THE DIFFERENCE?
Der php-Prozess blockiert während des Aufrufes und wird erst fortgesetzt, wenn $response verfügbar ist.
BLOCKING vs. NON-BLOCKING
var http = require('http');
http.get('http://google.com', function(res) { console.log(res); });console.log('on the way!');
WHERE IS THE DIFFERENCE?
node.js kann weiterarbeiten, während der Request im
Hintergrund bearbeitet wird.
BLOCKING vs. NON-BLOCKINGDIFFERENT SOLUTIONS
/ Threads bzw. Prozesse (apache/fcgi/…)
/ während ein Thread „schläft“ können andere Threads arbeiten
/ einfacher und intuitiver zu verstehen
/ Threads und Prozesse sind leider sehr teuer.
/ Event-Loops
/ Thread-Pools (Background-Tasks)
/ Alles wesentliche passiert in nur einem Prozess
/ Gehirnjogging – wann passiert was?
INTRODUCING EVENTS
INTRODUCING EVENTS
/ Ereignisse auf die in der Software reagiert werden kann
/ beliebig viele Quellen (üblicherweise nur eine)
/ beliebig viele Empfänger
EVENTS IM BROWSER/ UI-events: resize, scroll, ...
/ user interaction-events: mouse*, key*, …
/ timer events: setTimeout(), setInterval()
/ render-events: requestAnimationFrame()
/ resource-events: load, readystatechange, …
/ navigation-events: hashchange, popstate, ...
/ communication-events: message, ...
SERVER EVENTS
/ I/O-events (networking, http, filesystem, …)
/ timer-events
/ custom-events (emitted by modules)
EVENT-HANDLING: CALLBACKS
/ vereinfachte Event-Verarbeitung
/ wird anstelle eines return-Wertes aufgerufen
/ Konvention: function callback(err, data) {}
EVENT-HANDLING: EventEmitter/ Event-Quelle und Dispatcher
/ Listener für bestimmte Events werden mit on(evName, callback) angemeldet.
/ Events werden mit emit() (node.js) oder trigger() bzw. triggerHandler() (jQuery) ausgelöst
/ Events werden nur an die Listener für das Event in dem Event-Emitter gesendet.
EVENT-HANDLING: EventEmitter
// Beispiel EventEmitter / node.js-defaultvar EventEmitter = require('events').EventEmitter, ee = new EventEmitter();
ee.on('food', function(data) { console.log('omnom!', data); });ee.emit('myCustomEvent', { cookies: true });
// Beispiel jQueryvar $doc = $(document);
$doc.on('food', function() { console.log('omnom', data); });$doc.triggerHandler('food', { cookies: true });
REAL-LIFE EVENTS
REAL-LIFE EVENTSRestaurant-Example
/ Ein Gast betritt ein Restaurant
/ … Kellner weist einen Tisch zu
/ … Kellner verteilt Speisekarten
/ Der Gast gibt eine Bestellung auf; der Kellner…
/ … gibt Getränkebestellungen an Barkeeper weiter
/ … gibt Essensbestellungen an Küche weiter
restaurant.on('newGuestEnters', function(guests) { assignTable(guests);
guest.on('seated', function() { guests.handout(menue); });
// this might actually happen more than once... guests.on('order', function(order) { kitchenWorker.send(order.getMeals()); kitchenWorker.on('mealsReady', function(meals) { guest.deliver(meals); });
barkeeper.send(order.getDrinks()); barkeeper.on('drinksReady', function(drinks) { guest.deliver(drinks); }); });});
REAL-LIFE EVENTSRestaurant-Example
REAL-LIFE EVENTS
LOOKS COMPLICATED?
Restaurant-Example
while(true) { var guests = waitForGuests();
assignTable(guests); handoutMenues(guests);
var order; while(order = guests.getOrder()) { guests.deliver( makeDrinks(order) ); guests.deliver( makeFood(order) ); }}
REAL-LIFE EVENTSthe same, but with synchronous calls
REAL-LIFE EVENTSthe same, but with synchronous calls
/ nur 1 Gast je Kellner
/ n gleichzeitige Gäste brauchen n Kellner (= Threads)
/ n Kellner und m>n Gäste: Warteschlange am Eingang
/ schlafende Kellner
REAL-LIFE EVENTS
SO…
restaurant.on('newGuestEnters', function(guests) { waiter.assignTable(guests);
guests.on('seated', function() { guests.handout(menue);
// this might actually happen more than once... guests.on('order', function(order) { kitchenWorker.send(order.getMeals()); kitchenWorker.on('mealsReady', function(meals) { guests.deliver(meals); }); barkeeper.send(order.getDrinks()); barkeeper.on('drinksReady', function(drinks) { guests.deliver(drinks); }); }); });});
REAL-LIFE EVENTSasync is winning!
REAL-LIFE EVENTSasync is winning!
/ nur 1 Kellner für n gleichzeitige Gäste
/ zu viele Gäste führen zu langsamerer Asuführung
/ Zubereitung wird von Hintergrund-Services erledigt (barkeeper/kitchenWorker)
/ jegliche Aktion wird über Events ausgelöst
/ Kellner sind nur untätig, wenn
restaurant.on('newGuestEnters', function(guests) { waiter.assignTable(guests);
guests.on('seated', function() { guests.handout(menue);
// this might actually happen more than once... guests.on('order', function(order) { kitchenWorker.send(order.getMeals()); kitchenWorker.on('mealsReady', function(meals) { guests.deliver(meals); }); barkeeper.send(order.getDrinks()); barkeeper.on('drinksReady', function(drinks) { guests.deliver(drinks); }); }); });});
REAL-LIFE EVENTSasync is winning!
THE EVENT-LOOP
THE EVENT-LOOP
MAIN-PROCESSfile = fs.createReadStream(...);file.on('data', myCallback);
BASIC ARCHITECTURE
the code in the main program is executed and registers event-
handlers for certain events.
1WORKER
THREADS
long-running operations (I/O) are handled by
worker-threads
2
WORKERTHREADS
THE EVENT-LOOPBASIC ARCHITECTURE
MAIN-PROCESSfile = fs.createReadStream(...);file.on('data', myCallback);
EVENT-LOOP
EVENT-QUEUE
once all code is executed, the event-loop starts
waiting for incoming events.
3
WORKERTHREADS
THE EVENT-LOOPBASIC ARCHITECTURE
MAIN-PROCESSfile = fs.createReadStream(...);file.on('data', myCallback);
EVENT-LOOP
EVENT-QUEUE
background-threads eventually fire events
to send data to the main-program
4
fs.data
http.request
fs.end
WORKERTHREADS
THE EVENT-LOOPBASIC ARCHITECTURE
MAIN-PROCESSmyCallback(data);
EVENT-LOOP
EVENT-QUEUEhttp.request
fs.end
for each event, the associated callbacks are executed in the
main process
5fs.datafs.data
WORKERTHREADS
THE EVENT-LOOPBASIC ARCHITECTURE
MAIN-PROCESSprocess.nextTick(callback);setTimeout(callback, 0);
http.request
fs.end
it is possible to add custom events to the
event-queue
5
tickEvent
timerEvent
THE EVENT-LOOPZUSAMMENFASSUNG
/ keine Parallelisierung
/ Verarbeitung in Reihenfolge der Erzeugung
/ alles wesentliche in nur einem Prozess
/ separate Prozesse für CPU-Intensive Tasks
/ zwingt zum Schreiben von asynchronem Code
THE EVENT-LOOP
NEVER BLOCK THE EVENT-LOOP
$('.oh-my').on('click', function(req, res) { $.ajax('/somewhere', { async: false, complete: function(jqXHR, respText) { console.log('we‘re done!'); } });
console.log('waited soo long!');});
NEVER BLOCK THE EVENT-LOOP – BROWSER EDITIONTHE EVENT-LOOP
everytime someone makes a synchronous XHR, god kills a kitten.
will block the event-loopAND the UI-Thread
is executed AFTER the theXHR completes
var running = true;
process.nextTick(function() { running = false;});
while(running) { // ...whatever...}
NEVER BLOCK THE EVENT-LOOP – SERVER EDITIONTHE EVENT-LOOP
is never called!
…because this never returns
var http = require('http'), webserver = http.createServer();
webserver.on('request', function(req, res) { // some long-running calculation (e.g. image-processing) // or synchronous call
res.end('finally done!');});
THE EVENT-LOOP
no further request-processing while this is running
NEVER BLOCK THE EVENT-LOOP – SERVER EDITION
var http = require('http'), asyncService = require('./asyncService'), webserver = http.createServer();
webserver.on('request', function(req, res) { asyncService.startLongRunningOperation(); asyncService.on('completed', function() { res.end('finally done!'); });});
ASYNC FTW!THE EVENT-LOOP
doesn‘t block
// asyncService.jsvar spawn = require('child_process').spawn, EventEmitter = require('events').EventEmitter,
service = new EventEmitter();
service.startLongRunningOperation = function() { var child = spawn('sleep', [ 2 ]); child.on('exit', function(code) { service.emit('completed'); });};
module.exports = service;
ASYNC FTW!THE EVENT-LOOP
takes 2 seconds to complete
$('#foo').on('click', function(ev) { // first part of something that takes some time
window.setTimeout(function() { // second part... }, 0);});
ASYNC FTW (BROWSER-EDITION)!THE EVENT-LOOP
„pushed back“, allows other events to be processed in the meantime
ASYNC FTW!THE EVENT-LOOP
/ Wenns mal zu lange dauert:
/ child-process in node.js
/ web-worker im browser
/ „split and defer“: setTimeout(continuation, 0);
zOMG, CALLBACKS EVERYWHERE
CALLBACK-HELLrestaurant.on('newGuestEnters', function(guests) { assignTable(guests);
guests.on('seated', function() { guests.handout(menue);
// this might actually happen more than once... guests.on('order', function(order) { kitchenWorker.send(order.getMeals()); kitchenWorker.on('mealsReady', function(meals) { guests.deliver(meals); }); barkeeper.send(order.getDrinks()); barkeeper.on('drinksReady', function(drinks) { guests.deliver(drinks); }); }); });});
CALLBACK-HELL
COULD BE EVEN WORSE
restaurant.on('newGuestEnters', function(guests) { assignTable(guests);
guests.on('seated', function() { guests.handout(menue); attachOrderHandling(guests); });});
function attachOrderHandling(guests) { guests.on('order', function(order) { kitchenWorker.send(order.getMeals()); kitchenWorker.on('mealsReady', function(meals) { guests.deliver(meals); });
barkeeper.send(order.getDrinks()); barkeeper.on('drinksReady', function(drinks) { guests.deliver(drinks); }); });}
CALLBACK-HELLSOLUTION: EXTRACT FUNCTIONS
WHAT IF FUNCTIONS DEPEND ON EACH OTHER?
Step( function readSelf() { fs.readFile(__filename, this); }, function capitalize(err, text) { if(err) { throw err; } return text.toUpperCase(); }, function showIt(err, newText) { if(err) { throw err; } console.log(newText); });
CALLBACK-HELLUSE Step()
https://github.com/creationix/step
WHAT IF WE NEED MULTIPLE RESPONSES
Step( // Loads two files in parallel function loadStuff() { fs.readFile(__filename, this.parallel()); fs.readFile("/etc/passwd", this.parallel()); }, // Show the result when done function showStuff(err, code, users) { if (err) throw err;
console.log(code); console.log(users); })
CALLBACK-HELLUSE Step()
QUESTIONS?
THANKS!
FEEDBACK https://joind.in/7340 MARTIN SCHUHFUSS@usefulthinkSLIDES https://speakerdeck.com/u/usefulthink