Parallelität in TypeScript: Vom Event Loop zu Web Workers
\n\nHaben Sie schon einmal bemerkt, wie Ihr Browser oder Ihre Node.js-Anwendung während der Ausführung einer komplexen mathematischen Operation oder der Verarbeitung eines großen JSON „einfriert“? Ein vertrautes Gefühl der Ohnmacht, wenn die Oberfläche nicht mehr auf Klicks reagiert und der Server nicht mehr auf neue Anfragen. Der Grund dafür ist, dass JavaScript (und sein strenger Bruder TypeScript) standardmäßig eine Single-Thread-Sprache ist. Aber das bedeutet nicht, dass wir zu langsamen Programmen verdammt sind. Im Gegenteil, der geschickte Einsatz von Parallelität in TypeScript ermöglicht es uns, schnelle und reaktionsfähige Anwendungen zu erstellen.
\n\nIn diesem Artikel werden wir nicht nur Begriffe aufzählen. Wir werden untersuchen, wie async/await tatsächlich funktioniert, was unter der Haube des Event Loop steckt, ob man in TypeScript „Goroutinen“ wie in Go verwenden kann und wann man echte Threads über Web Workers einsetzen sollte. Los geht's!
\n\n1. Asynchronität vs. Parallelität: Was ist der Unterschied?
\n\nBevor wir uns in den Code vertiefen, lassen Sie uns die Begriffe klären. Viele verwechseln diese Konzepte, aber das ist ein Fehler.
\n\n- \n
- Asynchronität (Nebenläufigkeit) – die Fähigkeit eines Programms, eine Aufgabe zu starten, zu einer anderen zu wechseln, während die erste wartet (z. B. auf eine Antwort vom Server), und später zu ihr zurückzukehren. Zu einem bestimmten Zeitpunkt wird nur eine Anweisung ausgeführt. \n
- Parallelität – die Ausführung von zwei oder mehr Aufgaben buchstäblich zur gleichen Zeit auf verschiedenen Prozessorkernen. \n
TypeScript bietet uns von Haus aus Asynchronität. Parallelität erfordert dagegen die Erstellung separater Threads – im Browser sind das Web Workers, in Node.js worker_threads. Merken Sie sich diesen Unterschied, sonst riskieren Sie, Code zu schreiben, der aufgrund der Blockierung des Hauptthreads „langsam“ wird.
\n\n2. Event Loop und Callbacks – das Fundament der Parallelität in TypeScript
\n\nDas Hauptgeheimnis der Asynchronität in JavaScript/TypeScript ist der Event Loop (Ereignisschleife). Stellen Sie sich vor, Sie haben eine Warteschlange von Aufgaben. Der Hauptthread nimmt eine Aufgabe, führt sie aus – wenn sie synchron ist, gibt er sofort das Ergebnis zurück. Handelt es sich um eine asynchrone Operation (z. B. setTimeout oder fetch), wird sie „zur Seite“ geschickt (in die Web API oder libuv), während der Hauptthread mit der Ausführung des nächsten Codes fortfährt.
Wenn die asynchrone Operation abgeschlossen ist, gelangt ihr Callback in die Warteschlange (Task Queue oder Microtask Queue). Der Event Loop überprüft ständig, ob der Call-Stack leer ist, und wenn dies der Fall ist, nimmt er die erste Aufgabe aus der Warteschlange und führt sie aus.
\n\n// Ein einfaches Beispiel, das dies demonstriert\nconsole.log('1. Start');\n\nsetTimeout(() => {\n console.log('3. Timer ausgelöst');\n}, 0);\n\nPromise.resolve().then(() => {\n console.log('2. Mikrotask ausgeführt');\n});\n\nconsole.log('4. Ende');\n\n// Ausgabe: 1, 4, 2, 3\n\nBeachten Sie: Das Promise (Mikrotask) wurde vor dem setTimeout (Makrotask) ausgeführt, obwohl die Verzögerung des Timers 0 ms beträgt. Dies ist ein wichtiges Wissen für die Optimierung von TypeScript.
3. Async/Await – Zucker, der alles verändert
\n\nMit dem Aufkommen von ES2017 teilte sich die JS-Welt in ein „Vorher“ und „Nachher“. Async/await ist syntaktischer Zucker für Promises, der asynchronen Code wie synchronen Code aussehen lässt. Es fügt keine Threads hinzu, verbessert aber die Lesbarkeit radikal.
\n\n// Funktion, die eine Verzögerung simuliert\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// Asynchrone Funktion mit async/await\nasync function fetchUserData(userId: number): Promise<string> {\n console.log(`Fordere Daten von Benutzer ${userId} an...`);\n await delay(2000); // Simulation einer Netzwerkanfrage\n return `Daten von Benutzer ${userId}`;\n}\n\nasync function main() {\n console.log('Start');\n const user1 = await fetchUserData(1);\n console.log(