3.3 Asynchrones JavaScript: sequentiell oder parallel
Nach dieser Einheit kannst du:
- entscheiden, welche Schritte eines Ablaufs voneinander abhängen, und unabhängige Schritte mit
Promise.allgleichzeitig starten. awaitin Schleifen bewusst einsetzen oder durchPromise.allübermapersetzen.- einen vollständigen asynchronen Ablauf mit
Promise<T>,Promise.allund einem einzigencatchlesen und seine Laufzeit begründen.
Eine App lädt beim Start dein Profil, deine Benachrichtigungen und die Neuigkeiten. Jede Anfrage dauert eine Sekunde. Wartest du drei Sekunden auf die Startseite oder nur eine? Beides ist möglich, und der Unterschied steckt in einer einzigen Codezeile.
flowchart LR
accTitle: Drei unabhängige Anfragen beim App-Start
start["App-Start"] --> profile["Profil laden"]
start --> notes["Benachrichtigungen laden"]
start --> news["Neuigkeiten laden"]
profile --> ready["Startseite fertig"]
notes --> ready
news --> ready
Abb. 3.11: Drei Anfragen, die nicht aufeinander angewiesen sind: Müssen sie trotzdem aufeinander warten? (eigene Darstellung).
In Einheit 3.2 hast du zwei Fallen kennengelernt, die zu Abstürzen oder verschwundenen Fehlern führen. Die dritte Falle stürzt nicht ab, sie verschenkt Zeit. Am Ende dieser Einheit setzt ein vollständiges Beispiel alles aus Kapitel 3 zusammen.
Begriffe vorab
Abschnitt betitelt „Begriffe vorab“- sequentiell: Schritte laufen nacheinander, jeder wartet, bis der vorige fertig ist.
- parallel (hier genauer: nebenläufig): Schritte laufen gleichzeitig, gewartet wird auf alle gemeinsam.
- abhängig: Ein Schritt braucht das Ergebnis eines anderen, etwa die Bestellungen die Benutzer-ID.
Promise.all: nimmt mehrere Promises und liefert ein Promise, das erfüllt ist, sobald alle erfüllt sind, und abgelehnt wird, sobald eines scheitert.
Sequentiell oder parallel: eine Entscheidung
Abschnitt betitelt „Sequentiell oder parallel: eine Entscheidung“Die dritte Falle aus Einheit 3.2 ist kein Absturz, sondern verschenkte Zeit. Jedes await wartet, bevor die nächste Zeile startet, auch wenn die nächste Zeile gar nicht auf das Ergebnis angewiesen ist.
Sequentiell statt parallel
Abschnitt betitelt „Sequentiell statt parallel“// sequential: second request starts only after the first finishedconst user = await loadUser(id);const settings = await loadSettings(id); // does NOT depend on user
// parallel: both start immediately, wait for bothconst [user2, settings2] = await Promise.all([loadUser(id), loadSettings(id)]);Beispielprojekt: 03_async_javascript_examples/ex08_sequential_vs_parallel
Bei unabhängigen Operationen verschenkt das Nacheinander Zeit: Zwei Anfragen zu je einer Sekunde dauern nacheinander zwei Sekunden, parallel eine (im Beispielprojekt gemessen: rund 2000 ms gegen rund 1000 ms). Die beiden Aufrufe im Array starten beide Anfragen sofort, und Promise.all wartet, bis alle erfüllt sind. Die Koordinationswerkzeuge dafür (Promise.all und Verwandte) behandelt das nächste Kapitel im Detail, hier genügt das Muster.
await in Schleifen
Abschnitt betitelt „await in Schleifen“// one after another: n * latencyfor (const id of ids) { results.push(await loadUser(id));}
// all at once: roughly 1 * latencyconst results = await Promise.all(ids.map((id) => loadUser(id)));Beispielprojekt: 03_async_javascript_examples/ex09_await_in_loop
Mit vier Benutzern zu je einer halben Sekunde misst das Beispielprojekt rund 2000 ms für die Schleife und rund 500 ms für Promise.all. Die Schleifenform ist trotzdem nicht falsch: Manchmal ist die Reihenfolge wichtig, oder der Server soll nicht mit vielen gleichzeitigen Anfragen belastet werden. Sie muss aber eine Entscheidung sein, kein Versehen.
Wo begegnet dir das? Wenn eine App beim Start dein Profil, deine Benachrichtigungen und die Neuigkeiten lädt, hängen diese drei Anfragen nicht voneinander ab. Gemeinsam gestartet bestimmt die langsamste die Wartezeit, nacheinander addieren sich alle drei.
Auflösung
Benutzer und Bestellungen hängen voneinander ab: erst await loadUser(...), dann mit dessen ID await loadOrders(...). Das Wetter braucht keines der beiden Ergebnisse und kann gleichzeitig mit diesem Paar starten, etwa mit Promise.all([userAndOrders(), loadWeather()]), wobei userAndOrders eine async-Funktion mit den beiden await ist.
Ein vollständiges Beispiel
Abschnitt betitelt „Ein vollständiges Beispiel“Ein Ausschnitt aus einem Render-Dashboard: Der Status mehrerer Render-Jobs wird simuliert abgefragt, mit Wartezeit, Fehlerfall und Messung. Hier kommt alles aus Kapitel 3 zusammen: Promise<T>, async/await, Promise.all und ein einziges catch.
interface RenderJob { id: string; state: "queued" | "running" | "done" | "failed";}
function sleep(milliseconds: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, milliseconds));}
async function fetchJobState(id: string): Promise<RenderJob> { await sleep(300 + Math.random() * 200); // simulated network delay if (id === "job-13") { throw new Error(`job ${id}: render node not reachable`); } return { id, state: "running" };}
async function report(ids: string[]): Promise<void> { const start = performance.now(); try { const jobs = await Promise.all(ids.map((id) => fetchJobState(id))); for (const job of jobs) { console.log(`${job.id}: ${job.state}`); } } catch (error) { console.error("report incomplete:", (error as Error).message); } console.log(`took ${Math.round(performance.now() - start)} ms`);}
await report(["job-11", "job-12", "job-14"]);Beispielprojekt: 03_async_javascript_examples/ex10_render_dashboard
Miss selbst: Mit drei Jobs dauert der parallele Bericht rund eine halbe Sekunde (gemessen: 453 ms), weil Promise.all nur auf den langsamsten Job wartet. Nacheinander wären es drei Wartezeiten von je 300 bis 500 ms, also 0,9 bis 1,5 Sekunden.
Wo begegnet dir das? Statusseiten funktionieren nach diesem Muster, etwa die Übersicht einer Build-Pipeline oder einer Paketverfolgung mit mehreren Sendungen: Viele unabhängige Abfragen starten gleichzeitig, und ein Ausfall muss sichtbar gemeldet werden, statt still zu verschwinden.
Auflösung
Ausgegeben werden nur report incomplete: job job-13: render node not reachable und die Dauer. job-11 und job-14 erscheinen nicht: Promise.all wird abgelehnt, sobald ein einziges Promise scheitert, und die for-Schleife läuft nie. Die Dauer liegt dann zwischen 300 und 500 ms, weil Promise.all nicht auf die übrigen Jobs wartet (in Node.js 24 gemessen: 359 bis 436 ms).
Stolperfallen
Abschnitt betitelt „Stolperfallen“- Unnötig sequentiell: Unabhängige Anfragen nacheinander
awaiten verschenkt Zeit. forEachmitasync-Callback:forEachwartet nicht und verwirft die Promises samt Fehlern. Ersatz:for...ofmitawaitoderPromise.allübermap.Promise.allliefert bei einem Fehler gar nichts: Scheitert ein einziges Promise, wirdPromise.allabgelehnt, und auch die erfolgreichen Ergebnisse kommen nicht an.- Abhängige Schritte parallel gestartet: Braucht ein Schritt das Ergebnis eines anderen, muss er warten;
Promise.allpasst nur für unabhängige Schritte.
Zusatzmaterialien
Abschnitt betitelt „Zusatzmaterialien“- MDN: Promise.all() - wann das gemeinsame Promise erfüllt und wann es abgelehnt wird.
- javascript.info: Promise API -
Promise.allund seine Verwandten mit Beispielen; Vorschau auf Kapitel 4. - MDN: for…of - die Schleife, die mit
awaitder Reihe nach wartet.
Zum Anhören
Abschnitt betitelt „Zum Anhören“Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Codebeispiele liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“Du kannst jetzt:
- entscheiden, welche Schritte eines Ablaufs voneinander abhängen, und unabhängige Schritte mit
Promise.allgleichzeitig starten. awaitin Schleifen bewusst einsetzen oder durchPromise.allübermapersetzen.- einen vollständigen asynchronen Ablauf mit
Promise<T>,Promise.allund einem einzigencatchlesen und seine Laufzeit begründen.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 03-2a - Promise-Werkstatt, Stufen PlusPlus und MandalorianPlus
- Aufgabe 03-3a - Promise-Fehlerjagd
Quellen
Abschnitt betitelt „Quellen“Promise.allfür unabhängige Operationen, Ablehnung beim ersten Fehler → MDN: Promise.all()for...of→ MDN: for…of- Lehrplanbezug (Threading) → Lehrplan der Höheren Lehranstalt für Informationstechnologie, B.2 Medientechnik, 2.1 Softwareentwicklung, IV. Jahrgang (zitiert auf der Kursseite Lehrplan und Ablauf)
- Messwerte (
ex08,ex09,ex10) → eigene Messung mit Node.js 24.11.1 und tsx (Windows, Oktober 2026) - Abb. 3.11 (App-Start, Mermaid), Abb. 3.12 (sequentiell und parallel), Abb. 3.13 (
Promise.allim Render-Dashboard) → eigene Darstellung
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA