Ein Callback ist eine Funktion, die du einer anderen Funktion übergibst
Funktionen sind in JavaScript ganz normale Werte. Du kannst sie in Variablen speichern, in Arrays packen und – darum geht's hier – als Argument an andere Funktionen übergeben. Genau dann, wenn du eine Funktion an eine andere Funktion übergibst, damit diese sie später aufruft, sprechen wir von einer Callback-Funktion.
greet weiß nicht (und muss auch nicht wissen), was formatter macht. Die Funktion ruft formatter einfach mit einem Namen auf und verwendet das Ergebnis. Welches Verhalten dabei rauskommt, bestimmst du, indem du unterschiedliche Callbacks übergibst. Genau diese Flexibilität ist der Grund, warum es Callbacks überhaupt gibt.
Synchrone Callbacks laufen sofort
Nicht jeder Callback ist asynchron. Viele der Array-Methoden, die du ohnehin täglich benutzt, arbeiten mit Callbacks – und zwar synchron. Der Callback wird also abgearbeitet, bevor der äußere Aufruf zurückkehrt:
map, filter und reduce nehmen alle eine Callback-Funktion entgegen und rufen sie sofort einmal pro Element auf. Sobald map zurückkehrt, sind bereits alle Aufrufe des Callbacks passiert. Da wird nichts für später in eine Warteschlange gelegt.
Das ist ein ganz klassisches Muster mit Higher-Order Functions – nach dem Motto: „Hier ist die Arbeit, hier ist die Anleitung, gib mir das Ergebnis." Mit dem Event Loop hat das nichts zu tun.
Asynchrone Callbacks in JavaScript laufen später
Wenn Leute von „Callbacks" reden, meinen sie meistens die asynchrone Variante. Du übergibst eine Funktion an eine API, die Zeit braucht – einen Timer, einen Netzwerk-Request, einen Dateizugriff – und die API ruft deine Funktion zurück, sobald die Arbeit erledigt ist.
Ausgabereihenfolge: vorher, nachher und eine Sekunde später dann Timer ausgelöst. setTimeout hält dein Programm nicht an. Die Funktion übergibt den Callback an die Runtime, kehrt sofort zurück, und der Rest des Skripts läuft ungerührt weiter. Eine Sekunde später schnappt sich die Event Loop den Callback und führt ihn aus.
Dieses Muster – „jetzt zurückkehren, später zurückrufen" – ist das mentale Modell hinter jeder asynchronen Callback-API in JavaScript, von addEventListener bis hin zu den älteren Datei-APIs in Node.js.
Die Error-First-Konvention (Node.js)
Bevor es Promises gab, hat sich Node.js auf eine bestimmte Callback-Signatur festgelegt: Das erste Argument ist ein Fehler (oder null), danach folgt das eigentliche Ergebnis. In älterem Code und einigen Libraries begegnet dir dieses Muster bis heute.
Zuerst prüft der Aufrufer err und steigt sofort aus, wenn etwas schiefgelaufen ist. Erst danach wird das Ergebnis verwendet. Das ist reine Konvention – die Sprache selbst erzwingt das nicht –, aber sobald dir die Signatur (err, result) => ... begegnet, erkennst du das Muster überall wieder.
Callback Hell in JavaScript
Richtig unangenehm wird es, wenn ein asynchroner Schritt vom Ergebnis des vorherigen abhängt. Jeder Callback muss dann im vorherigen verschachtelt werden, und schon baut sich eine regelrechte Treppe nach rechts auf:
Das ist die berüchtigte „Pyramid of Doom" – besser bekannt als Callback Hell. Gleich mehrere Dinge machen sie so schmerzhaft:
- Der Kontrollfluss mäandert quer über die Seite, statt sauber von oben nach unten lesbar zu sein.
- Auf jeder Ebene wiederholt sich dasselbe
if (err) return ...-Boilerplate. - Wirft ein Callback eine Exception, landet sie nicht automatisch bei den äußeren Callbacks – du musst Fehler auf jeder Ebene einzeln behandeln.
- Jedes Refactoring bedeutet, den kompletten Block neu einzurücken.
Mit ausgelagerten, benannten Funktionen lässt sich das Ganze zwar etwas entzerren, aber das Grundproblem bleibt: Mit rohen Callbacks ist asynchrone Komposition einfach unhandlich. Genau dafür wurden Promises erfunden.
Zwei Stolperfallen, die du kennen solltest
Ruf den Callback nicht versehentlich auf. Wenn du einen Callback übergibst, übergibst du die Funktion selbst – nicht den Rückgabewert ihres Aufrufs.
Vorsicht mit this. Wenn dein Callback eine normale Funktion ist, die this verwendet, hängt der Wert von this davon ab, wie der Callback aufgerufen wird – nicht davon, wo er definiert wurde. Arrow Functions umgehen dieses Problem elegant, weil sie this aus dem umgebenden Scope erben:
Arrow Functions sind aus genau diesem Grund die Standardwahl für Inline-Callbacks.
Callback vs. Promise
Callbacks tauchen nach wie vor in synchronen APIs auf (map, forEach, sort), bei Event-Listenern (element.addEventListener("click", ...)) und in hardwarenahen Runtime-Hooks. Für asynchrone Aufgaben, die ein einzelnes Ergebnis liefern, hat sich das Ökosystem dagegen fast vollständig auf Promises verlagert.
Der Vergleich in Kürze:
- Callbacks — direkt und minimalistisch, aber schlecht kombinierbar. Fehlerbehandlung muss man bei jedem Schritt von Hand machen.
- Promises — ein Wert, der ein zukünftiges Ergebnis repräsentiert. Per
.then()verketten, Fehler einmal zentral mit.catch()abfangen — und schon ist die Pyramide flach.
Trotzdem solltest du Callbacks verstehen: Promises bauen darauf auf, und in ereignisgetriebenem Code sind sie allgegenwärtig. Neue asynchrone APIs schreibt man aber kaum noch mit rohen Callbacks.
Weiter geht's: Promises
Promises nehmen die Idee „mach das, sobald jenes bereit ist" und verpacken sie in ein Objekt, das du herumreichen, verketten und kombinieren kannst. Genau darum geht's auf der nächsten Seite — und damit auch um die Brücke zu async/await, wie modernes JavaScript heute den Großteil der asynchronen Arbeit erledigt.
Häufig gestellte Fragen
Was ist eine Callback-Funktion in JavaScript?
Ein Callback ist eine Funktion, die du einer anderen Funktion als Argument mitgibst, damit diese sie später aufrufen kann. Bei setTimeout(() => console.log('hi'), 1000) übergibst du z. B. eine Arrow Function als Callback — setTimeout merkt sie sich und ruft sie auf, sobald der Timer abläuft. Callbacks waren lange der Standardweg in JavaScript nach dem Motto „mach das, sobald jenes fertig ist“.
Was ist der Unterschied zwischen synchronen und asynchronen Callbacks?
Ein synchroner Callback läuft sofort ab, noch während die aufrufende Funktion arbeitet — [1, 2, 3].map(x => x * 2) ruft den Callback dreimal auf, bevor map überhaupt zurückkehrt. Ein asynchroner Callback wird dagegen gespeichert und erst später ausgeführt, wenn ein bestimmtes Ereignis eintritt — so arbeiten setTimeout, fs.readFile und DOM-Event-Listener. Asynchrone Callbacks blockieren den restlichen Code nicht.
Was ist Callback Hell und wie vermeidet man sie?
Callback Hell ist diese typische Pyramidenform, die entsteht, wenn asynchrone Callbacks voneinander abhängen und dadurch mehrere Ebenen tief verschachtelt werden. Der Kontrollfluss und vor allem das Error Handling werden dadurch schnell unübersichtlich. Die Lösung: Promises mit .then()-Ketten oder — noch besser — async/await. Beides flacht die Pyramide wieder zu lesbarem Code ab.