Dieses Jahr durfte ich mit auf die IPC. Neben vielen tollen Eindrücken und neuen Kontakten gab es für mich auch ein paar äußerst interessante Vorträge zu hören. In diesem Artikel möchte ich kurz auf den Vortrag von Martin Schuhfuß (Slides auf speakerdeck.com), dem neuen PHPopstar, eingehen.
Grundlagen
Zunächst mussten ein paar Grundlagen geklärt werden. Warum in JavaScript Funktionen wie ganz normale Objekte behandelt werden, wie man diese in Variablen speichert oder als Parameter, bzw. Rückgabewert in anderen Funktionen nutzt. Ferner ging Martin Schuhfuß darauf ein, wie Funktionen sich direkt nach ihrer Deklaration ausführen lassen (immediate Funktion) und diskutierte im Detail die Funktionsweise von Closures.
Non-Blocking Functions
Als nächstes ging es um das Thema blockierender und nicht-blockierender Funktionen und deren Vor- und Nachteile. Während in PHP der komplette Programmfluss unterbricht, beispielsweise um auf die Antwort eines HTTP Requests zu warten, terminiert die Funktion in JavaScript komplett und schickt dann das Ergebnis des Requests an die dafür vorgesehenen Callbacks. Nahezu alle Funktionsaufrufe, die nicht sofort erledigt werden können (blocking), sollten solche Callbacks für die Rückgabewerte benutzen, um den Programmfluss nicht zu blockieren. In der Zwischenzeit kann das Programm so weitere Events oder andere Aufgaben verarbeiten. Andere Sprachen verwenden Threads, um während der blockierenden Funktionsaufrufe noch andere Aufgaben erledigen zu können. In JavaScript hingegen beschränkt man sich meistens auf die asynchronen Events, auch wenn zum Beispiel via WebWorker auch hier Threads verwendet werden können.
„Callback Hell“
Durch die vielen asnychronen Funktionsaufrufe und ihre Callbacks landet man in JavaScript schnell in einer, wie Martin Schuhfuß es so passend bezeichnet, „Callback Hell“. Das klassische Problem: Man möchte hintereinander mehrere asynchrone Funktionen aufrufen, die jeweils erst ausgeführt werden, wenn die vorherige Funktion ihre Arbeit beendet hat.
call1(function() { call2(function() { call3(...) }); });
Das schaut so schon nicht besonders hübsch aus, und dabei sind das erst drei Funktionsaufrufe. Ein paar weitere hinzugefügt, dann noch ein paar dazwischen, die parallel laufen dürften – da blickt einfach niemand mehr durch.
Zum Glück gibt es verschiedene Möglichkeiten, dem entgegenzuwirken. So lassen Funktionen sich ja, wie bereits erwähnt, in Variablen auslagern, wodurch man zumindest die Verschachtelungstiefe reduzieren kann. Und dann gibt es noch zahlreiche Frameworks, die eine Lösung für das Problem bieten.
Event Emitter
Martin Schuhfuß ging hierbei kurz auf die EventEmitter-Klasse von Node.js und jQuery ein und stellte dann ein Real-World-Beispiel für Blocking bzw. Non-Blocking Events vor:
Ein Kellner weist den Gästen den Tisch, verteilt die Speisekarten und wartet dann auf die Bestellung (blocking). Bis die Gäste sich ihre Speisen überlegt haben, ist der Kellner für andere Gäste nicht verfügbar. Wenn die Gäste bestellt haben, bringt der Kellner die Bestellung zur Küche und wartet auf die Zubereitung der Speisen (blocking). Nach dem Servieren der Speisen wird noch gewartet, bis die Gäste ihre Rechnung fordern und dann letztendlich bezahlen. Mit dieser Methodik wird für jeden Gast ein eigener Kellner benötigt. Dem gegenüber der gleiche Vorgang mit asynchronen Vorgängen: Ein Gast trifft ein, der Kellner führt ihn zu seinem Platz und händigt ihm die Speisekarte aus. Der Kellner wartet nun nicht, bis der Gast sich seine Speisen überlegt hat, sondern schaut, welches Event als nächstes eintritt. Kommen neue Gäste, so weist er diesen ebenfalls wieder einen Tisch und händigt die Speisekarte aus. Ruft ein Gast nach dem Kellner, kommt er an den Tisch und nimmt eine Bestellung auf. Es reicht tatsächlich ein einziger Kellner für ein beliebig großes Restaurant. Zwar wird die Abarbeitungsschleife etwas langsamer, wenn zu viele Gäste einen Wunsch haben, aber es funktioniert trotzdem noch.
Event Loop
Im Anschluss an das Beispiel wird die grundlegende Architektur der JavaScript Event Loop erklärt: Das Hauptprogramm registriert Event Handler für verschiedene Events. Lang laufende Operationen werden an Worker-Threads übergeben, die nach Beendigung entsprechend die Event Handler aufrufen. Sobald aller Code des Hauptprogramms ausgeführt wurde, startet die Event Loop. Bei eintreffenden Ereignissen werden dann entsprechend die Event Handler ausgeführt. Wichtig ist, dass hierbei keine Parallelität entstehen kann. Erst wenn der Code des aktuell aufgerufenen Event Handlers vollständig terminiert ist, kann ein weiterer Event Handler zur Ausführung kommen (auch wenn in der Zwischenzeit vielleicht weitere Worker-Threads mit ihren Aufgaben fertig sind). Die werden dann der Reihenfolge nach abgearbeitet, sobald keine Event Handler mehr in der Ausführung sind. Der Hauptprogrammfluss findet also nur in einem einzigen Prozess statt. Um bei besonders rechenintensiven Aufgaben die CPU-Kerne besser ausnutzen zu können, lassen sich dann eigene Worker-Threads starten.
Gewarnt wird vor dem Blockieren der Event Loop durch langlaufende Funktionen oder gar Endlosschleifen. Im Falle einer Endlosschleife wird der Code nie beendet, und so kann auch nie mit der Abarbeitung weiterer Events begonnen werden. Das Programm hängt komplett.
Zum Abschluss wird noch kurz auf das Node-Modul „Step“ eingegangen, mit dem sich mehrere asynchrone Funktionen auf schöne Weise aneinanderreihen lassen (sowohl parallel als auch sequentiell).
Fazit
Zusammengefasst lässt sich sagen, dass JavaScript sich hervorragend für eventgetriebene oder messagebasierte Anwendungen eignet. Zwar muss man sich, gerade wenn man aus einer Sprache wie PHP kommt, ein wenig umgewöhnen, die Vorteile liegen aber klar auf der Hand: Es lassen sich sehr viele Anfragen und Aufgaben erledigen, ohne dass man sich damit die Probleme einhandelt, mit denen man es normalerweise bei Threads zu tun bekommt (Synchronizing, etc.).
JavaScript lässt sich für weit mehr verwenden, als nur ein paar UI-Animationen im Browser. Mit Node.js ist es auch auf Serverseite konkurrenzfähig, und lädt ein, auf professionelle Weise komplexe Anwendungen zu entwickeln.
Schreibe einen Kommentar