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

The Event-Driven Nature of Javascript (German) ...

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.

Avatar for Martin Schuhfuss

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()