Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Event-Driven Nature of Javascript (German) – IPC2012

The Event-Driven Nature of Javascript (German) – IPC2012

Eine der größten Herausfoderungen im Umgang mit Node.js und komplexeren Webanwendungen ist der häufig eingesetzte Event-getriebene Ansatz, der sich sehr stark von aus anderen Sprachen bekannten prozeduralen und parallelen Ansätzen unterscheidet. In dieser Session soll ein Einblick gegeben werden, wie Event-Loops arbeiten, warum sich JavaScript hervorragend für die Umsetzung Event-getriebener Software eignet und wie Softwarearchitekturen basierend auf Events aussehen können.

Martin Schuhfuss

October 16, 2012
Tweet

More Decks by Martin Schuhfuss

Other Decks in Technology

Transcript

  1. Ü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
  2. 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()
  3. FIRST-CLASS FUNCTIONS // a simple function function something() { console.log("something");

    } // functions assigned as values var aFunction = function() { /* ... */ }, somethingElse = something; // function returning a function function getFunction() { return function(msg) { console.log(msg); } } var fn = getFunction(); fn("foo"); // "foo" getFunction()("foo"); // works the same way!
  4. FIRST-CLASS FUNCTIONS // functions as parameters function call(fn) { return

    fn(); } // passing an anonymous function call(function() { console.log("something"); }); // "something"
  5. IMMEDIATE FUNCTIONS (function __immediatelyExecuted() { console.log("something"); } ()); // in

    short: (function() { ... } ()); unmittelbar nach Deklaration ausgeführte Funktionen
  6. CLOSURES / definieren scopes für Variablen / ermöglichen private Variablen

    / „einfrieren“ von Werten / Ermöglichen „konfigurierbare“ Funktionen
  7. CLOSURES var deg2rad = (function() { var RAD_PER_DEG = Math.PI/180;

    return function(deg) { return deg * RAD_PER_DEG; } }()); console.log(RAD_PER_DEG); // undefined deg2Rad(180); // 3.1415926… Scoping: „private“ Variablen
  8. 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
  9. 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
  10. 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?
  11. 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
  12. ASYNC FUNCTIONS ASYNC FUNCTIONS ARE JAVASCRIPTS ANSWER TO position: absolute;

    Jed Schmidt, getting functional with (fab) – JSConf.eu 2010
  13. NON-BLOCKING FUNCTIONS / keine direkten Rückgabewerte / „Callbacks“ zur Fortsetzung

    des Programmablaufs / Background-Tasks übernehmen die Arbeit / keine Wartezeit innerhalb des Programms
  14. 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“
  15. 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
  16. 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 CPU CYCLES 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-BLOCKING WHERE IS THE DIFFERENCE? logarithmic scale!
  17. 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.
  18. 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.
  19. BLOCKING vs. NON-BLOCKING DIFFERENT 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?
  20. INTRODUCING EVENTS / Ereignisse auf die in der Software reagiert

    werden kann / beliebig viele Quellen (üblicherweise nur eine) / beliebig viele Empfänger
  21. 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, ...
  22. 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.
  23. EVENT-HANDLING: EventEmitter // Beispiel EventEmitter / node.js-default var EventEmitter =

    require('events').EventEmitter, ee = new EventEmitter(); ee.on('food', function(data) { console.log('omnom!', data); }); ee.emit('myCustomEvent', { cookies: true }); // Beispiel jQuery var $doc = $(document); $doc.on('food', function() { console.log('omnom', data); }); $doc.triggerHandler('food', { cookies: true });
  24. REAL-LIFE EVENTS Restaurant-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
  25. 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 EVENTS Restaurant-Example
  26. 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 EVENTS the same, but with synchronous calls
  27. REAL-LIFE EVENTS the 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
  28. 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 EVENTS async is winning!
  29. REAL-LIFE EVENTS async 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
  30. 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 EVENTS async is winning!
  31. THE EVENT-LOOP MAIN-PROCESS file = fs.createReadStream(...); file.on('data', myCallback); BASIC ARCHITECTURE

    the code in the main program is executed and registers event- handlers for certain events. 1 WORKER THREADS long-running operations (I/O) are handled by worker-threads 2
  32. WORKER THREADS THE EVENT-LOOP BASIC ARCHITECTURE MAIN-PROCESS file = fs.createReadStream(...);

    file.on('data', myCallback); EVENT-LOOP EVENT-QUEUE once all code is executed, the event-loop starts waiting for incoming events. 3
  33. WORKER THREADS THE EVENT-LOOP BASIC ARCHITECTURE MAIN-PROCESS file = 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
  34. WORKER THREADS THE EVENT-LOOP BASIC ARCHITECTURE MAIN-PROCESS myCallback(data); EVENT-LOOP EVENT-QUEUE

    http.request fs.end for each event, the associated callbacks are executed in the main process 5 fs.data fs.data
  35. WORKER THREADS THE EVENT-LOOP BASIC ARCHITECTURE MAIN-PROCESS process.nextTick(callback); setTimeout(callback, 0);

    http.request fs.end it is possible to add custom events to the event-queue 5 tickEvent timerEvent
  36. THE EVENT-LOOP ZUSAMMENFASSUNG / 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
  37. $('.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 EDITION THE EVENT-LOOP everytime someone makes a synchronous XHR, god kills a kitten. will block the event-loop AND the UI-Thread is executed AFTER the the XHR completes
  38. var running = true; process.nextTick(function() { running = false; });

    while(running) { // ...whatever... } NEVER BLOCK THE EVENT-LOOP – SERVER EDITION THE EVENT-LOOP is never called! …because this never returns
  39. 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
  40. 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
  41. // asyncService.js var 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
  42. $('#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
  43. 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);
  44. CALLBACK-HELL restaurant.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); }); }); }); });
  45. 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-HELL SOLUTION: EXTRACT FUNCTIONS
  46. 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-HELL USE Step() https://github.com/creationix/step
  47. 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-HELL USE Step()