Zum Inhalt springen

Aufgabe 03-2a - Promise-Werkstatt

Zu Zen-Modus wechseln
  • Du erzeugst eigene Promises mit resolve, beschreibst ihre drei Zustände und typisierst asynchrone Funktionen mit Promise<T>.
  • Du schreibst abhängige Abläufe mit async/await und sicherst sie mit try/catch/finally ab.
  • Du schreibst denselben Ablauf als Promise-Kette und mit async/await und vergleichst beide Schreibweisen begründet.
  • Du erkennst die Folgen eines vergessenen await und startest unabhängige Schritte mit Promise.all gleichzeitig.

Eine Pizzeria-Küche ist ein Paradebeispiel für Nebenläufigkeit: Der Teig muss gehen, bevor belegt wird, der Ofen braucht seine Zeit, und manchmal verbrennt eine Pizza. Wer das sauber steuern kann, kann später auch jeden API-Aufruf steuern.

Du baust mit Promises und async/await eine simulierte Pizzeria-Küche: asynchrone Abläufe mit Abhängigkeiten, Wartezeiten und Fehlerfällen (siehe Kapitel Asynchrones JavaScript, Einheiten 3.1 und 3.2; für die Stufen PlusPlus und MandalorianPlus auch Einheit 3.3). Alles landet in einer Datei pizzeria.ts.

Projektordner anlegen siehe https://qiz.at/projektordner

  • Lies vorher kurz https://qiz.at/assignmentsabgabe durch.
  • Nur vollständige Abgaben (siehe https://qiz.at/rules) können positiv bewertet werden.
  • Abgabe- und Testdetails siehe https://qiz.at/assignmentsabgabe
  • Die Übung liegt als Unterordner uebung-03-2a in deinem Jahres-Repository SJ26_27_4AHITM_SEW_<Benutzername>_exercises, eingerichtet wie in Aufgabe 01: package.json mit "type": "module" (für await auf oberster Ebene) und den Skripten dev (tsx pizzeria.ts) und typecheck, dazu die tsconfig.json aus npx tsc --init. Du arbeitest allein.
  • Schriftliche Antworten stehen als Kommentar in pizzeria.ts. npm run typecheck muss fehlerfrei durchlaufen.
  • Verkürz die Wartezeiten beim Testen auf ein Zehntel (200 ms statt 2 s), sonst wartest du mehr, als du programmierst.
  • sleep(milliseconds): Promise<void>: löst nach der Wartezeit auf, mit setTimeout gebaut.
  • Drei Küchenschritte als typisierte async-Funktionen: makeDough() (2 s), addToppings(dough, topping) (1 s), bake(pizza) (3 s). Jede loggt Start und Ende mit Zeitstempel und reicht ein beschreibendes Objekt weiter (etwa { step: "dough" }).
  • bake schlägt in 25 Prozent der Fälle fehl: throw new Error("burned!"). Der Zufall kommt aus Math.random().
  • orderPizza(topping): führt die drei Schritte mit await in der richtigen Reihenfolge aus, misst die Gesamtdauer mit performance.now() und liefert die fertige Pizza.

Tipp: Einheit 3.1 zeigt im Abschnitt “Promises: ein Platzhalter für später”, wie man setTimeout in ein Promise verpackt: setTimeout bekommt als Callback genau die Funktion, die das Promise erfüllt. new Date().toISOString().slice(17, 23) liefert Sekunden und Millisekunden, das reicht für die Zeitstempel.

Vollständig, wenn:

  • pizzeria.ts strikt ohne Fehler und ohne any kompiliert und alle asynchronen Funktionen mit Promise<T> typisiert sind.
  • die Schritte in der richtigen Reihenfolge laufen und die Zeitstempel es belegen.
  • bake in etwa einem Viertel der Läufe mit burned! scheitert und der Fehler beim Aufrufer ankommt.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Verbrannt-Behandlung in orderPizza: Beim ersten Fehlschlag wird genau einmal neu gebacken; scheitert auch das, wird der Fehler weitergereicht, nicht verschluckt.
  • Ein kleines Hauptprogramm bestellt Pizzen, fängt den Fehler und meldet ihn kundenfreundlich. Ein finally druckt in jedem Fall die Dauer.
  • Teste alle drei Fälle: Erfolg, ein Fehlschlag mit erfolgreichem zweitem Versuch, zwei Fehlschläge.

Tipp: Überleg dir, welcher Aufruf von einem try/catch umschlossen sein muss und welcher nicht, damit der zweite Fehlschlag beim Aufrufer ankommt. Stört der Zufall beim Testen, übergib bake optional eine eigene Zufallsfunktion (random = Math.random); im Test liefert sie dann feste Werte.

Vollständig, wenn:

  • es genau einen Wiederholungsversuch gibt und der zweite Fehlschlag weitergereicht und vom Aufrufer kundenfreundlich gemeldet wird.
  • die Dauer in jedem Fall ausgegeben wird, auch bei zwei Fehlschlägen.
  • alle drei Fälle getestet sind.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Schreib dieselbe Kette ein zweites Mal als then-Verkettung (orderPizzaThen), inklusive des einen Wiederholungsversuchs, und teste die drei Fälle.
  • Vergleich beide Fassungen in zwei bis drei Sätzen: Lesbarkeit, Fehlerbehandlung, wo würdest du welche einsetzen?
  • Vorhersage zuerst, schriftlich: Was passiert, wenn in orderPizza beim ersten Backversuch das await fehlt, also return bake(...) statt return await bake(...) im inneren try? Wann erscheint die Dauer im Log, findet der Wiederholungsversuch noch statt, wo landet ein Verbrannt-Fehler?
  • Probier es mit einem Fehlschlag beim ersten Backen aus, vergleich mit deiner Vorhersage und erklär das Verhalten mit dem Event-Loop-Modell: Wann verlässt die Ausführung den try-Block, wann scheitert das Promise?

Tipp: Ein catch in einer Kette gibt ein neues Promise zurück, auf das die Kette wartet. Wo du es anhängst, entscheidet, welche Fehler es fängt.

Vollständig, wenn:

  • orderPizzaThen sich in allen drei Fällen gleich verhält wie orderPizza und der Vergleich begründet ist.
  • Vorhersage und Beobachtung zum fehlenden await dokumentiert und mit dem Modell erklärt sind.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Wo hilft TypeScript, wo nicht? Weise das Ergebnis von bake(...) ohne await einer Variablen vom Typ Pizza zu und greif ohne await auf ein Feld des Ergebnisses zu. Notiere beide Compile-Meldungen und beurteile, warum return bake(...) aus der vorigen Stufe keine Meldung erzeugt.
  • Bau eine Variante, in der zwei Beläge parallel vorbereitet werden (zweimal addToppings gleichzeitig, gemeinsames Warten), und miss den Zeitgewinn gegenüber der Variante nacheinander.

Tipp: Das Koordinationswerkzeug für das gemeinsame Warten zeigt Einheit 3.3; die Vertiefung folgt in Aufgabe 04-1a. Ein Zeitgewinn ohne gemessenen Vergleichswert ist keine Messung.

Vollständig, wenn:

  • beide Compile-Meldungen notiert sind und die Beurteilung zu return bake(...) stichhaltig ist.
  • der Zeitgewinn der parallelen Variante gegen die Variante nacheinander gemessen und begründet ist.

Wie unter Allgemein.

  1. In welchen drei Zuständen kann ein Promise sein, und wie oft wechselt es?
  2. Was liefert eine async-Funktion immer, auch bei return 42?
  3. Wohin springt die Ausführung, wenn ein erwartetes Promise (await) scheitert?
  4. Warum pausiert await nur die Funktion und nicht das Programm?
  5. Woran erkennst du im Log, dass zwei Schritte parallel statt nacheinander liefen?

Durchschnittlich bis Wesentlich_vollständig: ca. 100 min

  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules