Ein Thread, um alle zu beherrschen: Mythen und Wahrheit über Konkurrenz in JavaScript
\n\nVor langer Zeit, in der Ära statischer Websites und „Senden“-Schaltflächen, die die gesamte Oberfläche blockierten, lebte JavaScript glücklich in einem einzigen Thread. Es war genug: Eine Anfrage kam – wurde ausgeführt, eine Anfrage ging weg – die Oberfläche erstarrte. Aber die Zeiten haben sich geändert. Heute erwarten wir von Webanwendungen sofortige Reaktion: Scrollen darf nicht ruckeln, Animationen müssen flüssig sein und das Laden von Daten unsichtbar. Und hier kommt Konkurrenz ins Spiel.
\n\nViele Entwickler verwechseln Konkurrenz (concurrency) mit Parallelität (parallelism). In JavaScript, besonders im Browser, haben wir es häufiger mit Ersterem zu tun. Es ist wie ein Jongleur: Er hält nicht alle Bälle gleichzeitig in der Luft (das wäre Parallelität), aber er wechselt so geschickt zwischen ihnen, dass die Illusion von Gleichzeitigkeit entsteht. In diesem Artikel werden wir drei Schlüsselwerkzeuge zur Organisation von Konkurrenz in JS untersuchen: async/await, Goroutinen (über Generatoren und Web Workers) und klassische Threads. Los geht's!
Async/Await: eleganter asynchroner Code
\n\nasync/await ist syntaktischer Zucker über Promises, der in ES2017 eingeführt wurde. Es macht Code nicht multithreaded, sondern asynchron. Wenn Sie await schreiben, „schläft“ JavaScript in dieser Funktion ein und gibt den Thread für andere Aufgaben frei. Sobald das Promise aufgelöst ist, wacht die Funktion auf und setzt die Ausführung fort. Dies ist das ideale Werkzeug für I/O-Operationen: API-Anfragen, Dateilesen, Timer.
Einfaches Beispiel: Daten vom Server laden
\n\n// Funktion, die ein Promise zurückgibt\nfunction fetchUser(id) {\n return new Promise((resolve) => {\n setTimeout(() => resolve({ id, name: 'Alexei' }), 1000);\n });\n}\n\n// Asynchrone Funktion\nasync function loadUserProfile(userId) {\n console.log('Ladevorgang gestartet...');\n const user = await fetchUser(userId);\n console.log('Benutzer geladen:', user.name);\n return user;\n}\n\nloadUserProfile(42);\nconsole.log('Dieser Code wird sofort ausgeführt, ohne auf das Laden zu warten!');\n\n\nBeachten Sie: console.log('Dieser Code wird sofort ausgeführt...') wird früher ausgeführt als console.log('Benutzer geladen'). Der Thread wird nicht blockiert – das ist Konkurrenz in Aktion.
Fehlerbehandlung
\n\nasync function fetchWithRetry(url, retries = 3) {\n for (let i = 0; i < retries; i++) {\n try {\n const response = await fetch(url);\n if (!response.ok) throw new Error('HTTP-Fehler');\n return await response.json();\n } catch (error) {\n if (i === retries - 1) throw error;\n console.log(`Versuch ${i + 1} fehlgeschlagen, versuche es erneut...`);\n }\n }\n}\n\n\nWann verwenden: Alle Operationen mit externen Ressourcen (API, Datenbanken, Dateisystem in Node.js). async/await ist Ihr primäres Werkzeug für asynchronen Code.
Goroutinen in JavaScript: Mythos oder Realität?
\n\nDer Begriff „Goroutine“ stammt aus Go und bezeichnet einen leichtgewichtigen Ausführungs-Thread. In JavaScript gibt es keine nativen Goroutinen, aber es gibt Generatoren (Generator Functions) und Web Workers, die ein ähnliches Verhalten simulieren können. Generatoren ermöglichen es, die Ausführung einer Funktion anzuhalten und wieder aufzunehmen, was Kontrolle über den Ausführungsfluss gibt.
\n\nGeneratoren als Grundlage für Konkurrenz
\n\nfunction* taskGenerator() {\n console.log('Starte Aufgabe 1');\n yield 'Aufgabe 1 abgeschlossen';\n console.log('Starte Aufgabe 2');\n yield 'Aufgabe 2 abgeschlossen';\n console.log('Starte Aufgabe 3');\n yield 'Aufgabe 3 abgeschlossen';\n}\n\nconst iterator = taskGenerator();\nconsole.log(iterator.next().value); // Führt Aufgabe 1 aus\n// In diesem Moment kann etwas anderes gemacht werden\nconsole.log(iterator.next().value); // Führt Aufgabe 2 aus\nconsol