Start / Seminare / JavaScript Grundlagen & Modernes ECMAScript
Modul
Asynchrones JavaScript
5 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
JavaScript — Asynchron
0:00 Jetzt wird es anspruchsvoller — und gleichzeitig praxisnäher als alles Bisherige. Denn in echten Anwendungen dauert Arbeit. Eine Datei lesen, eine Anfrage ins Netz schicken, eine Datenbank befragen: All das braucht Zeit, in der nichts passiert. Würde das Programm dabei stillstehen, wäre jede Anwendung unbrauchbar träge. Also braucht es einen Weg, zu warten, ohne alles anzuhalten. Genau darum geht es hier.
0:25 Wir sehen uns drei Generationen von Lösungen an — und wir beginnen mit der Fehlerbehandlung, weil ohne sie jeder folgende Abschnitt unvollständig wäre.
Asynchrones JavaScript
0:34 Der vierte Tag beginnt mit fünf Kapiteln. Zuerst die begriffliche Grundlage: Was heißt synchron, was asynchron, und warum ist Warten der Normalfall? Dann die Fehlerbehandlung — sie steht bewusst vorn, denn asynchroner Code ohne Fehlerbehandlung ist bestenfalls halbfertig. Danach die drei Muster in ihrer historischen Reihenfolge: Callbacks als älteste Form, Promises als deren Nachfolger und schließlich die Schreibweise, die asynchronen Code aussehen lässt wie synchronen.
1:02 Alle drei treffen Sie in echtem Code an, oft nebeneinander.
Synchron gegen asynchron
1:06 Beginnen wir mit dem Grundgedanken. Und gleich ein Hinweis zur Erwartungshaltung: Die Ereignisschleife, die im Hintergrund alles orchestriert, benennen wir nur. Sie ist hier nicht der Lerngegenstand — die Muster sind es. Synchroner Code ist das, was wir bisher geschrieben haben: Schritt für Schritt, jede Zeile wartet auf die vorige.
1:27 Asynchroner Code startet eine Aufgabe und macht weiter — und wenn das Ergebnis irgendwann da ist, läuft der zugehörige Rest. Das Bild dazu ist der Kaffeeautomat im Büro. Synchron hieße: Sie stellen sich davor und starren, bis der Becher voll ist. Asynchron heißt: Sie drücken den Knopf, gehen zurück an den Schreibtisch und holen den Kaffee, wenn er fertig ist. In der Zwischenzeit haben Sie gearbeitet.
1:51 Genau das tut Ihr Programm. Diese Liste zeigt, dass Warten nicht die Ausnahme ist, sondern der Alltag. Netzwerkzugriffe, Dateizugriffe, Datenbankabfragen — alles dauert, und zwar in einer Größenordnung, die für einen Prozessor eine Ewigkeit ist. Im Browser kommt eine zweite Sorte Warten hinzu: auf Klicks, Eingaben, Zeitgeber. Auf dem Server sind es Dateien, Verbindungen und Datenströme.
2:15 Der letzte Punkt zieht die Konsequenz: Würde jedes Warten alles anhalten, wäre jede Anwendung unbrauchbar. Eine Oberfläche, die während jeder Anfrage einfriert, kennen Sie vermutlich noch aus schlechten Zeiten — genau diese Erfahrung wollen die Muster verhindern. Drei Muster, ein Zweck, drei Generationen. Der Callback ist die älteste Idee: Sie hinterlegen eine Funktion, die später aufgerufen wird — wie eine Rückrufbitte.
2:41 Das Promise ist ein Platzhalter für ein Ergebnis, das noch aussteht, ähnlich einem Abholschein. Und die dritte Form lässt asynchronen Code aussehen wie ganz gewöhnlichen. Der Fußtext ist praktisch wichtig: Alle drei existieren nebeneinander. Sie werden nicht wählen können, welches Ihnen begegnet — in gewachsenem Code trifft man auf jedes davon, manchmal in derselben Datei. Deshalb behandeln wir alle drei.
3:06 Sagen Sie die Ausgabereihenfolge eines Programms mit einem Zeitgeber voraus — und begründen Sie sie. Das ist die Vorhersage-Technik aus Modul zehn, angewandt auf ein Gebiet, in dem die Intuition regelmäßig danebenliegt. Der Hinweis nennt den entscheidenden Punkt, und er ist kontraintuitiv: Die Zeile nach dem Zeitgeber läuft, bevor der Zeitgeber ausgelöst hat — selbst bei null Millisekunden.
3:29 Null heißt nämlich nicht sofort, sondern so früh wie möglich, nachdem der aktuelle Ablauf durch ist. Wenn Sie diesen einen Satz verinnerlicht haben, verstehen Sie die Hälfte des asynchronen Verhaltens. Die Erwartung, dass Zeilen in Schreibreihenfolge laufen, ist tief verwurzelt und hier schlicht falsch. Der zweite Punkt ist die praktische Folge und der häufigste Anfängerfehler in asynchronem Code: ein Ergebnis verwenden, bevor die Aufgabe fertig ist — Sie greifen dann auf undefined zu.
3:57 Der dritte Punkt ist eine begriffliche Klarstellung, die wichtig ist: Asynchron heißt nicht gleichzeitig. JavaScript arbeitet weiterhin eine Sache nach der anderen ab, es verschiebt nur die Reihenfolge. Und der vierte ist ein Lernhinweis: Die Ereignisschleife verstehen zu wollen, bevor die Muster sitzen, führt in die falsche Richtung.
Fehlerbehandlung
4:17 Jetzt zur Fehlerbehandlung, und sie steht bewusst vor den asynchronen Mustern. Ein Kernsatz begleitet dieses Kapitel: Fangen, um etwas zu tun — nicht, um etwas zu verbergen. Der Unterschied entscheidet darüber, ob Ihre Fehlerbehandlung hilft oder schadet. Ein Fehlerobjekt trägt eine Meldung und meist einen Aufrufstapel — also die Information, über welchen Weg das Programm an diese Stelle kam.
4:41 Das ist bei der Diagnose oft wertvoller als die Meldung selbst. Drei Bausteine gehören dazu. Das Werfen beendet den normalen Ablauf schlagartig. Der Fangblock nimmt den Fehler entgegen. Und der Schlussblock läuft in jedem Fall, egal ob es geklappt hat oder nicht. Diesen letzten braucht man für Aufräumarbeiten — eine Datei schließen, eine Verbindung zurückgeben. Dinge, die auch dann passieren müssen, wenn etwas schiefgegangen ist.
5:08 Hier steckt ein Detail, das viele nicht kennen und das sehr nützlich ist. Im Fangblock wird nicht einfach weitergeleitet, sondern ein neuer Fehler geworfen — mit einer fachlich passenden Meldung. Der Import ist gescheitert, das ist die Aussage, die den Aufrufer interessiert. Aber der ursprüngliche technische Fehler geht nicht verloren: Er wird als Ursache mitgegeben.
5:28 So bekommen Sie beides — eine verständliche Meldung an der Oberfläche und die technische Spur darunter. Ohne diesen Mechanismus müssen Sie sich entscheiden, und meistens entscheidet man sich falsch. Der Schlussblock unten läuft übrigens in beiden Fällen. Vier Regeln, die zusammen eine klare Haltung ergeben. Werfen Sie, wenn eine Funktion nicht leisten kann, was ihr Name verspricht — dann ist der Fehler ehrlicher als ein Notbehelf.
5:54 Fangen Sie, wenn sich damit etwas Sinnvolles anfangen lässt: erneut versuchen, eine Alternative nehmen, dem Nutzer etwas Verständliches sagen. Die dritte Regel ist die wichtigste und steht bewusst als Verbot da: Nie fangen, um einen Fehler stillschweigend verschwinden zu lassen. Ein verschwundener Fehler wird später zu einem unerklärlichen Verhalten. Und wenn Sie nichts Sinnvolles tun können, reichen Sie weiter — gern mit zusätzlichem Kontext.
6:20 Behandeln Sie einen Fehler so, dass zweierlei gilt: Die Meldung ist fachlich verständlich, und die ursprüngliche Ursache bleibt erreichbar. Das ist genau das Muster aus dem Codebeispiel, und es ist eines, das Sie in professionellem Code ständig sehen werden. Der Hinweis ist eine Faustregel, die ich unterschreiben würde: Ein leerer Fangblock ist fast immer ein Fehler für sich.
6:41 Wenn Sie einen sehen — in fremdem Code oder in Ihrem eigenen —, lohnt sich die Frage, was dort eigentlich passieren sollte. Meistens lautet die Antwort: irgendwas, nur nicht nichts. Fehler zu fangen und nichts damit zu tun, ist der Verstoß gegen die Kernregel — und leider ein sehr verbreiteter. Der zweite Punkt ist subtiler: die Meldung auszugeben, aber weiterzulaufen, als sei nichts geschehen.
7:04 Das Programm arbeitet dann mit unvollständigen Daten weiter, und der eigentliche Schaden entsteht später. Der dritte ist ein Missverständnis über den Schlussblock — er läuft immer, auch im Fehlerfall, und eignet sich deshalb nicht für Erfolgsmeldungen. Und der vierte deutet auf den Rest dieses Moduls voraus: Bei asynchronem Code fängt ein gewöhnlicher Fangblock nichts ab, solange Sie nicht warten. Dazu kommen wir.
Callbacks
7:28 Jetzt das älteste der drei Muster. Man könnte fragen, warum wir es überhaupt behandeln, wenn es Nachfolger gibt. Die Antwort ist praktisch: Es steckt in unzähligen Bibliotheken und in jedem gewachsenen Projekt. Erkennen müssen Sie es, auch wenn Sie es nicht mehr schreiben. Der Begriff ist uns aus Modul acht vertraut: eine Funktion, die einer anderen übergeben wird, um später aufgerufen zu werden.
7:53 Neu ist der zweite Satz, und er beschreibt eine Konvention, die Sie kennen sollten. In älteren Node-Schnittstellen gilt der Fehler-zuerst-Stil: Der erste Parameter ist der Fehler, der zweite das Ergebnis. Das wirkt zunächst verdreht — warum den Fehler zuerst? Weil man ihn dadurch nicht übersehen kann. Er steht an erster Stelle, und die erste Zeile im Callback ist üblicherweise seine Prüfung.
8:16 Eine pragmatische Lösung aus einer Zeit, in der es keine bessere gab. Der obere Block ist das Beispiel aus der Aufgabe von vorhin, mitsamt der Auflösung im Kommentar. Zuerst „vorher", dann „nachher", und erst danach der Zeitgeber — obwohl null Millisekunden angegeben sind. Der Grund: Der Zeitgeber wird eingeplant, nicht ausgeführt.
8:36 Erst wenn der aktuelle Ablauf durch ist, kommt er dran. Der untere Block zeigt den Fehler-zuerst-Stil in seiner typischen Form. Beachten Sie das Rücksprung-Schlüsselwort in der Fehlerzeile — es ist unverzichtbar, denn ohne es läuft der Erfolgspfad danach ebenfalls, mit Daten, die es gar nicht gibt. Vier Gründe, und sie beschreiben zusammen ein Phänomen, das einen eigenen Namen bekommen hat: die Callback-Hölle.
9:02 Jeder weitere Schritt schachtelt eine Ebene tiefer, weil er ja erst nach dem vorigen laufen kann. Bei drei Schritten ist das noch übersichtlich, bei sechs schiebt sich Ihr Code diagonal über den Bildschirm. Dazu kommt, dass Fehler auf jeder Ebene einzeln behandelt werden müssen — dieselbe Prüfung, sechsmal. Und das Ergebnis ist Code, der nicht mehr von oben nach unten lesbar ist.
9:23 Genau dieses Problem lösen die beiden folgenden Muster, und deshalb haben sie sich durchgesetzt. Sagen Sie die Reihenfolge dreier Ausgaben mit einem Zeitgeber voraus und erklären Sie, warum null Millisekunden nicht sofort bedeutet. Die Erklärung ist der eigentliche Prüfstein — die Reihenfolge kann man raten, das Warum nicht.
9:42 Der Hinweis liefert den Schlüssel in fünf Worten: Der Zeitgeber wird eingeplant, nicht sofort ausgeführt. Er kommt in eine Warteschlange, und die wird erst abgearbeitet, wenn der laufende Code fertig ist. Wer das einmal sauber formuliert hat, wundert sich später nicht mehr über Ausgaben, die scheinbar in der falschen Reihenfolge erscheinen.
10:02 Bei einem Fehler nicht zurückzuspringen und den Erfolgspfad mitlaufen zu lassen, ist der Klassiker des Fehler-zuerst-Stils — deshalb steht das Rücksprung-Wort im Beispiel so prominent. Der zweite Punkt ist die Null-Millisekunden-Annahme, die wir gerade behandelt haben. Der dritte ist eine Entwurfsfrage: Immer tiefer zu schachteln, statt auf Promises umzustellen, ist eine Entscheidung, die man irgendwann bereut.
10:25 Und der vierte ist ein grundsätzliches Missverständnis: Der Rückgabewert eines Callbacks geht ins Leere. Er wird von der aufrufenden Bibliothek entgegengenommen, nicht von Ihrem Code — genau deshalb braucht es Promises.
Promises
10:38 Jetzt die zweite Generation. Ein Promise löst genau das Problem, das wir eben beschrieben haben — es gibt Ihnen etwas in die Hand, mit dem Sie weiterarbeiten können, statt nur eine Funktion zu hinterlegen. Drei Zustände erklären dabei das ganze Verhalten. Ein Promise steht für das künftige Ergebnis einer Operation — der Abholschein aus der Übersicht. Sie bekommen ihn sofort, das Ergebnis später.
11:02 Und weil Sie ihn in der Hand haben, können Sie damit umgehen: ihn weitergeben, ihn zurückgeben, Behandlungen daranhängen. Drei Zustände sind möglich: Es läuft noch, es hat geklappt, oder es ist gescheitert. Und für jeden dieser Ausgänge gibt es eine Behandlung — eine für den Erfolg, eine für den Fehler und eine, die in jedem Fall läuft.
11:22 Diese Dreiteilung entspricht genau der aus der Fehlerbehandlung, nur eben zeitversetzt. Der obere Block zeigt die Erzeugung, und die brauchen Sie in der Praxis selten selbst — meist bekommen Sie fertige Promises von Bibliotheken. Wichtig ist die Regel: Genau eine der beiden Funktionen wird aufgerufen, entweder Erfolg oder Ablehnung. Interessanter ist die Kette unten.
11:44 Jeder Erfolgsblock bekommt das Ergebnis des vorigen und gibt selbst etwas zurück, das zum Eingang des nächsten wird. So entsteht ein Fluss statt einer Verschachtelung — das Gegenmittel gegen die Callback-Hölle. Und der Fehlerblock am Ende fängt Fehler aus der ganzen Kette ab. Einer statt sechs. Diese drei Zustände erklären, warum die Kette sich so verhält, wie sie sich verhält. Solange die Operation läuft, ist das Promise ausstehend und niemand wird aufgerufen.
12:11 Wird es erfüllt, läuft der nächste Erfolgsblock. Wird es abgelehnt, überspringt die Ausführung alle folgenden Erfolgsblöcke und landet direkt im Fehlerblock — genau wie ein geworfener Fehler durch mehrere Ebenen fällt. Der Fußtext ist ein praktischer Hinweis: Man fragt den Zustand fast nie von Hand ab. Man hängt Behandlungen an oder wartet — und die Sprache kümmert sich um den Rest.
12:34 Bauen Sie drei aufeinander aufbauende Schritte als Kette — und lassen Sie den mittleren fehlschlagen. Der Nachweis besteht aus zwei Teilen: Der Fehler landet nachweislich im Fehlerblock, und der Abschlussblock läuft in beiden Fällen. Damit sehen Sie das Sprungverhalten in Aktion. Der Hinweis nennt den Punkt, auf den es ankommt: Wirft ein Schritt, werden die folgenden Erfolgsblöcke übersprungen.
12:56 Das ist bequem — Sie brauchen nicht nach jedem Schritt zu prüfen — und es überrascht beim ersten Mal, weil scheinbar Code verschwindet. Im Erfolgsblock den Rückgabewert zu vergessen, ist der Klassiker: Der nächste Block bekommt dann undefined, und der Fehler zeigt sich eine Stufe später. Den Fehlerblock wegzulassen, erzeugt eine unbehandelte Ablehnung — in Node ist das inzwischen ein Grund für einen Programmabbruch, und das ist gut so.
13:22 Der dritte Punkt ist ein Rückfall in alte Gewohnheiten: Promises ineinander zu verschachteln, statt die Kette flach zu halten. Damit haben Sie die Callback-Hölle in neuer Verpackung. Und der vierte ist mechanisch: Nur der erste Abschluss zählt, alles Weitere wird ignoriert.
async und await
13:38 Und jetzt die dritte Generation, die alles zusammenführt. Der Reiz ist schnell gesagt: asynchron schreiben, synchron lesen. Ein Hinweis am Ende des Kapitels bewahrt Sie davor, das neue Schlüsselwort kultisch überall vorzusetzen — was häufiger vorkommt, als man denkt. Drei Aussagen, die zusammen alles erklären. Erstens: Eine so gekennzeichnete Funktion liefert immer ein Promise — auch wenn im Rumpf ein einfacher Wert zurückgegeben wird.
14:05 Zweitens: Das Warteschlüsselwort hält den Ablauf innerhalb dieser Funktion an, bis das Promise entschieden ist. Nur innerhalb — der Rest des Programms läuft weiter, und genau das ist der Trick. Und drittens die vielleicht wichtigste Eigenschaft: Aus einer Ablehnung wird ein normaler geworfener Fehler. Damit funktioniert plötzlich die gewöhnliche Fehlerbehandlung aus Kapitel zwei auch für asynchronen Code. Genau das war vorher nicht möglich.
14:31 Lesen Sie den unteren Block einmal ohne die asynchronen Schlüsselwörter — er sieht aus wie ganz gewöhnlicher synchroner Code. Laden, Ergebnis benutzen, im Fehlerfall melden. Genau das ist der Gewinn: Der Ablauf steht wieder von oben nach unten. Und die Fehlerbehandlung ist wieder die vertraute, weil eine Ablehnung sich unter dem Warteschlüsselwort wie ein geworfener Fehler verhält.
14:53 Der Fußtext enthält eine nützliche Information für moderne Projekte: In Modulen ist das Warten auch auf oberster Ebene erlaubt. Innerhalb von Funktionen brauchen Sie aber die Kennzeichnung — daran führt kein Weg vorbei. Hier steht ein Hinweis, der Ihnen unnötige Verzögerungen erspart. Gibt eine Funktion ein Promise nur weiter, ohne selbst etwas damit zu tun, kann sie es direkt zurückgeben — der Aufrufer wartet ohnehin.
15:17 Lohnend ist das Warten dann, wenn Sie das Ergebnis prüfen, umformen oder absichern wollen. Und für eine Fehlerbehandlung innerhalb der Funktion ist es zwingend: Ohne Warten ist die Funktion längst fertig, wenn der Fehler eintrifft, und Ihr Fangblock steht dann leer. Der letzte Punkt bringt es auf den Punkt: Vorsorglich gesetztes Warten verlängert nur den Ablauf, ohne etwas zu gewinnen.
15:40 Schreiben Sie eine bestehende Kette in die moderne Form um — mit denselben Ergebnissen, demselben behandelten Fehlerfall und ohne Erfolgsblock-Kette. Der Hinweis liefert die Übersetzungstabelle in einem Satz: Aus dem Fehlerblock der Kette wird der Fangblock, aus dem Abschlussblock der Schlussblock. Die Zuordnung ist erfreulich direkt.
15:58 Was Sie dabei erleben, ist der Grund für die Beliebtheit dieser Schreibweise — derselbe Ablauf, deutlich weniger Struktur um die eigentliche Logik herum. Und die Fehlerbehandlung ist wieder die, die Sie ohnehin kennen. Das Warteschlüsselwort ohne die Kennzeichnung zu verwenden, ist außerhalb von Modulen ein Syntaxfehler — immerhin ein eindeutiger.
16:18 Der zweite Punkt ist eine Erwartung, die überrascht: Eine gekennzeichnete Funktion liefert immer ein Promise, auch wenn Sie eine Zahl zurückgeben. Daraus folgt der dritte, praktisch häufigste Fehler: den Aufruf ohne Warten zu verwenden und mit einem Wert zu rechnen, wo ein Promise steht. Die Fehlermeldung ist dann oft rätselhaft. Und der vierte ist der bekannte: Fehler nicht abzufangen und eine unbehandelte Ablehnung zu erzeugen.
Warten, ohne zu blockieren
16:43 Drei Sätze zum Mitnehmen. Erstens die Einordnung: Callbacks, Promises und die moderne Schreibweise lösen dasselbe Problem in drei Generationen — und alle drei begegnen Ihnen in echtem Code. Zweitens die Zustandslehre: Ein Promise ist ausstehend, erfüllt oder abgelehnt, und daraus erklärt sich das gesamte Kettenverhalten.
17:02 Drittens die Brücke zur Fehlerbehandlung: Eine abgelehnte Zusage verhält sich unter dem Warteschlüsselwort wie ein geworfener Fehler — deshalb funktioniert dort die vertraute Behandlung. Im nächsten Modul wenden wir das an: Promises in freier Wildbahn, HTTP-Anfragen und Ereignisse.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →