JS
() =>
JavaScript · Web APIs · Async · Fetch
AbortController in JavaScript
Requests, Timeouts und async Ops sauber abbrechen

Wer Fetch-Requests nicht abbricht, riskiert Race Conditions, Memory Leaks und inkonsistente UI-Zustände. Der AbortController ist die native Browser-API, die dieses Problem ein für alle Mal löst – für Fetch, Event-Listener und beliebige async Operationen.

12 Min. Lesezeit AbortController · AbortSignal · Fetch API · Timeout Moderne Browser · Node.js 18+

1. Das Problem: unkontrollierte Fetch-Requests

Jede moderne Webanwendung sendet HTTP-Requests – für Autocomplete-Suchen, Produktlisten, Nutzerdaten. Was dabei oft übersehen wird: ein gestarteter Fetch-Request läuft im Browser weiter, selbst wenn die Komponente, die ihn ausgelöst hat, längst demontiert wurde oder der Nutzer bereits zu einer anderen Seite navigiert ist. Das Ergebnis ist ein klassisches Problem: der Request kommt zurück, findet aber keinen gültigen Zustand mehr vor – und schreibt trotzdem in einen State, der nicht mehr existiert.

Dieses Szenario führt zu sogenannten Race Conditions, wenn mehrere schnell aufeinanderfolgende Requests gestartet werden – etwa bei einer Sucheingabe, die mit jedem Tastenanschlag einen neuen Request auslöst. Der fünfte Request kann vor dem dritten ankommen, und die UI zeigt dann veraltete Ergebnisse an. Ohne AbortController gibt es keinen standardisierten Weg, einen laufenden Fetch-Request zu stoppen. Das war jahrelang ein echtes Defizit in der Fetch API – behoben durch die Einführung des AbortController als Web-Standard.

2. AbortController und AbortSignal: Grundlagen

Der AbortController besteht aus zwei Teilen, die zusammenarbeiten: dem Controller selbst und dem dazugehörigen Signal. Der Controller ist der Sender – er hält die Methode abort(), mit der ein Abbruch ausgelöst wird. Das Signal (controller.signal) ist das Empfangsobjekt – es wird an den abzubrechenden Vorgang übergeben und signalisiert diesem, wenn abort() aufgerufen wurde. Dieses Sender-Empfänger-Muster ist bewusst entkoppelt: der Code, der den Request ausführt, muss nichts über den Controller wissen, er hört nur auf das Signal.

Das Signal ist eine Instanz von AbortSignal und hat zwei wichtige Eigenschaften: signal.aborted ist ein Boolean, der anzeigt, ob bereits abgebrochen wurde, und signal.reason enthält den optionalen Grund für den Abbruch. Ab modernen Browsern kann man auch einen Grund beim Abbruch übergeben: controller.abort(new Error("Nutzer hat abgebrochen")). Dieser Grund ist dann über signal.reason abrufbar und erlaubt differenziertere Fehlerbehandlung.


// Basic AbortController usage
const controller = new AbortController();
const signal = controller.signal;

// Listen to abort event on the signal
signal.addEventListener('abort', () => {
  console.log('Aborted:', signal.reason);
});

// Check if already aborted before starting work
if (signal.aborted) {
  throw signal.reason;
}

// Trigger abort with optional reason
controller.abort(new Error('User navigated away'));

console.log(signal.aborted); // true
console.log(signal.reason);  // Error: User navigated away

3. Fetch-Requests mit AbortController abbrechen

Die Integration des AbortController in die Fetch API ist direkt: Das Signal wird als Option an fetch() übergeben. Sobald controller.abort() aufgerufen wird, bricht der Browser den Request ab – falls er noch auf eine Antwort wartet – und die Fetch-Promise wird mit einem AbortError abgelehnt. Wichtig: Requests, die bereits vollständig beantwortet wurden, können nicht rückgängig gemacht werden. Der AbortController stoppt den Request auf Netzwerkebene, nicht die Verarbeitung einer bereits empfangenen Antwort.

Ein typisches Muster für Sucheingaben: Bei jedem neuen Tastenanschlag wird der vorherige AbortController aufgerufen und ein neuer erstellt. So ist immer nur der jüngste Request aktiv. Das Pattern eliminiert Race Conditions vollständig, weil ältere Requests nie mehr in den State schreiben können – sie sind abgebrochen, bevor ihre Antwort verarbeitet wird. Dieses Muster ist so verbreitet, dass viele Datenfetch-Bibliotheken wie TanStack Query und SWR es intern verwenden.


// Search-as-you-type with AbortController — prevents race conditions
let currentController = null;

async function search(query) {
  // Abort previous request before starting new one
  if (currentController) {
    currentController.abort(new Error('Superseded by newer request'));
  }

  currentController = new AbortController();
  const { signal } = currentController;

  try {
    const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal,
    });

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const data = await response.json();
    renderResults(data);
  } catch (err) {
    // Distinguish abort from real errors
    if (err.name === 'AbortError') {
      console.log('Search superseded — ignoring');
      return;
    }
    showError(err.message);
  }
}

// Attach to input with debounce
document.getElementById('search').addEventListener('input', (e) => {
  search(e.target.value);
});

4. Automatische Timeouts mit AbortSignal.timeout()

Seit 2022 bietet die Browser-API eine statische Methode, die das häufigste AbortController-Pattern vereinfacht: AbortSignal.timeout(milliseconds). Sie erstellt ein Signal, das nach der angegebenen Zeit automatisch abgebrochen wird – ohne dass man manuell einen Controller anlegen und setTimeout kombinieren muss. Die Methode ist in allen modernen Browsern und Node.js 17.3+ verfügbar und ersetzt das manuelle Timeout-Pattern in den meisten Fällen vollständig.

Wenn man sowohl einen nutzergesteuerten Abbruch als auch einen automatischen Timeout benötigt, kombiniert man beides mit AbortSignal.any(): Das erzeugte Signal wird abgebrochen, sobald eines der übergebenen Signale abgebrochen wird. So lässt sich ein Request implementieren, der entweder nach fünf Sekunden abbricht oder wenn der Nutzer die Seite verlässt – ganz ohne manuelles Verwalten von Timern. Das ist ein elegantes, gut lesbares Muster für die Realität produktiver APIs.


// AbortSignal.timeout() — clean timeout without manual controller
async function fetchWithTimeout(url, ms = 5000) {
  try {
    const response = await fetch(url, {
      signal: AbortSignal.timeout(ms),
    });
    return await response.json();
  } catch (err) {
    if (err.name === 'TimeoutError') {
      throw new Error(`Request timed out after ${ms}ms`);
    }
    throw err;
  }
}

// Combining user-abort + timeout with AbortSignal.any()
async function fetchProduct(id, userSignal) {
  const combined = AbortSignal.any([
    userSignal,
    AbortSignal.timeout(8000),
  ]);

  const response = await fetch(`/api/products/${id}`, { signal: combined });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// Usage: pass the component's signal + automatic 8s timeout
const ctrl = new AbortController();
fetchProduct(42, ctrl.signal).then(console.log).catch(console.error);

// Cancel from UI:
cancelButton.onclick = () => ctrl.abort();

5. AbortController in React-Komponenten

In React ist der AbortController unverzichtbar in useEffect-Hooks, die Daten fetchen. Das Problem ohne Abbruch: Eine Komponente startet einen Request im Effect, wird aber vor dem Abschluss des Requests demontiert. Der Request kommt trotzdem an und versucht, den State der nicht mehr existierenden Komponente zu aktualisieren. React gibt in diesem Fall eine Warnung aus: „Can't perform a React state update on an unmounted component". Mit AbortController in der Cleanup-Funktion des Effects ist das Problem elegant gelöst.

Das Muster ist immer gleich: Controller in der Effect-Funktion erstellen, Signal an Fetch übergeben, controller.abort() in der Cleanup-Funktion aufrufen. React führt die Cleanup-Funktion aus, wenn die Komponente demontiert wird oder die Effect-Dependencies sich ändern. Da React im Strict Mode jeden Effect zweimal aufruft (um Cleanup-Funktionen zu testen), ist korrektes Abbruchverhalten hier nicht nur gute Praxis, sondern Pflicht für eine funktionierende Entwicklungserfahrung.

6. Event-Listener mit AbortSignal verknüpfen

Seit der Einführung der signal-Option in addEventListener kann man Event-Listener mit einem AbortController-Signal verknüpfen. Wenn das Signal abgebrochen wird, entfernt der Browser den Event-Listener automatisch – ohne dass man removeEventListener aufrufen oder die Listener-Funktion als Referenz speichern muss. Dieses Pattern ist besonders wertvoll, wenn viele Listener auf einmal registriert werden und alle auf einmal entfernt werden sollen.

Ein praktisches Anwendungsbeispiel: Ein modales Dialogfenster registriert beim Öffnen mehrere Event-Listener (Klick auf Hintergrund, Escape-Taste, Scroll-Lock). Beim Schließen des Modals ruft man einmalig controller.abort() auf – und alle Listener sind entfernt. Das ersetzt komplexes Listener-Management durch ein einziges Signal und macht den Code erheblich wartbarer. Der gleiche Ansatz funktioniert für komplexe UI-Interaktionen mit vielen temporären Event-Bindings.


// Multiple event listeners controlled by one AbortController
function openModal(modalEl) {
  const controller = new AbortController();
  const { signal } = controller;

  // All listeners share the same signal
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') controller.abort();
  }, { signal });

  modalEl.querySelector('.backdrop').addEventListener('click', () => {
    controller.abort();
  }, { signal });

  document.body.addEventListener('focusin', (e) => {
    if (!modalEl.contains(e.target)) controller.abort();
  }, { signal });

  // When signal aborts, all listeners above are auto-removed
  signal.addEventListener('abort', () => {
    modalEl.classList.add('hidden');
    console.log('Modal closed — all listeners removed automatically');
  });

  modalEl.classList.remove('hidden');

  // Return controller so external code can close the modal too
  return controller;
}

const modal = openModal(document.getElementById('dialog'));
// Close from code: modal.abort();

7. AbortSignal.any() – mehrere Signale kombinieren

AbortSignal.any(signals) ist eine statische Methode, die seit 2023 in modernen Browsern verfügbar ist und eines der mächtigsten Werkzeuge im AbortController-Toolkit darstellt. Sie nimmt ein Array von Signalen entgegen und gibt ein neues Signal zurück, das abgebrochen wird, sobald eines der Eingangssignale abgebrochen wird. Das ermöglicht die Kombination verschiedener Abbruchgründe ohne eigenen Verwaltungsaufwand.

Ein reales Szenario aus einer komplexen Webanwendung: Ein Request soll abgebrochen werden, wenn (a) der Nutzer auf "Abbrechen" klickt, (b) ein globales Session-Timeout eintritt oder (c) der Request nach 10 Sekunden noch nicht abgeschlossen ist. Ohne AbortSignal.any() müsste man drei separate Signale manuell überwachen und koordinieren. Mit any() ist es eine Zeile. Das ist das Gegenteil von Boilerplate: ausdrucksstarker, direkter Code, der genau das macht, was er sagt.

8. Fehlerbehandlung: AbortError korrekt abfangen

Wenn ein Fetch-Request durch einen AbortController abgebrochen wird, lehnt die Promise mit einem DOMException ab, dessen name-Eigenschaft "AbortError" ist. Wichtig: Das ist kein regulärer Netzwerkfehler und sollte nicht wie ein echter Fehler behandelt werden. In einem Produktionssystem ist ein bewusst ausgelöster Abbruch kein Fehlerzustand, sondern ein normaler Kontrollfluss. Wenn ein abgebrochener Request denselben Fehler-Handler auslöst wie ein 500er-Serverfehler, werden falsche Alarme in Error-Tracking-Systemen wie Sentry ausgelöst.

Die korrekte Behandlung unterscheidet explizit zwischen AbortError, TimeoutError und anderen Fehlern. Bei einem selbst ausgelösten Abbruch kehrt man einfach zurück, ohne etwas zu tun. Bei einem Timeout zeigt man dem Nutzer eine informative Meldung und bietet die Möglichkeit, es erneut zu versuchen. Bei einem echten Netzwerkfehler loggt man den Fehler und behandelt ihn entsprechend. Dieses dreigeteilte Muster ist der professionelle Standard für den Umgang mit AbortController-Fehlern in produktiven Anwendungen.

9. AbortController im Vergleich: Alternativen

Vor dem AbortController gab es verschiedene Behelfslösungen, um das Race-Condition-Problem zu lösen. Ein gängiger Ansatz war das „Ignore-Flag"-Muster: eine Variable let ignore = false im Effect, die in der Cleanup-Funktion auf true gesetzt wird. Kommt die Antwort an und ignore ist true, wird die Antwort ignoriert. Das stoppt den Request nicht auf Netzwerkebene, verhindert aber das State-Update. Es ist eine Pragmalösung, keine saubere Lösung.

Ansatz Netzwerk-Abbruch Listener-Cleanup Empfehlung
AbortController Ja Ja (mit signal) Standard – immer bevorzugen
ignore-Flag Nein Nein Nur als Fallback für alte Systeme
XMLHttpRequest.abort() Ja Nein Legacy – nicht für neue Projekte
RxJS takeUntil Teilweise Ja Sinnvoll bei RxJS-Projekten
AbortSignal.timeout() Ja Ja Für reine Timeout-Fälle ideal

Die Tabelle zeigt: Der AbortController ist die einzige Lösung, die sowohl den Netzwerk-Request abbricht als auch Event-Listener aufräumen kann. Das ignore-Flag ist pragmatisch und funktioniert für React-State-Updates, löst aber das eigentliche Problem nicht: der Server empfängt und verarbeitet den Request weiterhin, was bei schreibenden Operationen (POST, PUT) fatale Konsequenzen haben kann. XMLHttpRequest.abort() ist veraltet. Der AbortController ist der native Web-Standard und die richtige Wahl für alle modernen JavaScript-Anwendungen.

Mironsoft

JavaScript-Entwicklung, Frontend-Architektur und Web-Performance

Race Conditions und Memory Leaks in Ihrer JavaScript-Anwendung beheben?

Wir analysieren bestehende Frontend-Code auf fragile Fetch-Patterns, Race Conditions und fehlende Cleanup-Logik – und ersetzen sie durch robuste AbortController-Lösungen.

Code-Audit

Analyse auf Race Conditions, fehlende AbortController und Memory Leaks in Fetch-Requests

Refactoring

Bestehende Fetch-Logik auf AbortController umstellen, inkl. React useEffect und Vue composables

Performance

Unnötige Netzwerktraffic durch konsequente Abbruchlogik reduzieren und API-Last senken

10. Zusammenfassung

Der AbortController ist das native JavaScript-Werkzeug, um laufende asynchrone Operationen kontrolliert zu beenden. new AbortController() erzeugt Controller und Signal; das Signal wird an fetch() oder addEventListener() übergeben; controller.abort() bricht alle verknüpften Operationen ab. AbortSignal.timeout(ms) implementiert automatische Timeouts in einer Zeile. AbortSignal.any(signals) kombiniert mehrere Signale zu einem. Das AbortError-Catching trennt gewollte Abbrüche von echten Fehlern und verhindert falsche Alarme im Error-Monitoring.

Der größte Nutzen des AbortController zeigt sich nicht im einfachen Fall – ein einziger Request mit einem Button – sondern in komplexen reaktiven Szenarien: Sucheingaben, die mit jedem Tastendruck einen neuen Request starten, Komponenten, die häufig gemountet und demontiert werden, und Operationen, die sowohl nutzergesteuert als auch zeitgesteuert abbrechbar sein müssen. Wer den AbortController konsequent einsetzt, schreibt Frontend-Code, der sich wie ein robustes Backend-System verhält: vorhersagbar, kontrolliert und ohne unerwartete Seiteneffekte.

AbortController — Das Wichtigste auf einen Blick

Grundprinzip

Controller hält abort()-Methode, Signal wird an fetch() und addEventListener() übergeben. Abbruch erfolgt durch controller.abort().

Timeout-Pattern

AbortSignal.timeout(ms) ersetzt manuelles setTimeout + Controller. Seit Node.js 17.3 und allen modernen Browsern verfügbar.

React-Pattern

In useEffect: Controller anlegen, Signal an fetch übergeben, controller.abort() in Cleanup-Funktion. Verhindert State-Updates in unmontierten Komponenten.

Fehlerbehandlung

err.name === 'AbortError' von echten Fehlern trennen. Abbrüche sind kein Fehlerzustand – nie in Error-Tracking loggen.

11. FAQ: AbortController in JavaScript

1Was ist der AbortController in JavaScript?
Eine native Web-API aus Controller und Signal: controller.abort() sendet das Abbruch-Signal an alle verknüpften Operationen – Fetch-Requests, Event-Listener, eigene async Logik.
2Wie bricht man einen Fetch-Request ab?
fetch(url, { signal: controller.signal }) – dann controller.abort() aufrufen. Die Promise wird mit AbortError abgelehnt. Im catch-Block mit err.name === 'AbortError' unterscheiden.
3Was macht AbortSignal.timeout()?
Erzeugt ein Signal, das nach N Millisekunden automatisch abbricht. Kein manueller Controller + setTimeout nötig. fetch(url, { signal: AbortSignal.timeout(5000) }) bricht nach 5s ab.
4Mehrere Signale kombinieren?
AbortSignal.any([signal1, signal2]) – das resultierende Signal bricht ab, sobald eines der Eingangssignale abbricht. Ideal für Timeout + User-Abbruch kombiniert.
5AbortError von echten Fehlern trennen?
catch(err) { if (err.name === 'AbortError') return; } – Abbrüche sind kein Fehlerzustand. Nie in Sentry oder ähnliche Tools loggen.
6Event-Listener mit AbortController entfernen?
addEventListener('event', fn, { signal }) – beim Abbruch des Signals wird der Listener automatisch entfernt. Kein removeEventListener nötig. Mehrere Listener, ein Controller.
7AbortController in React useEffect?
Controller im Effect anlegen, Signal an fetch, controller.abort() in Cleanup-Funktion. React ruft Cleanup beim Unmount und bei Dependency-Änderungen auf.
8Was passiert bei Abbruch eines fertigen Requests?
Nichts – sicher ignoriert. AbortController stoppt nur laufende Requests. Ein bereits abgeschlossener Request ist nicht rückgängig zu machen.
9AbortController in Node.js verfügbar?
Ja, seit Node.js 15 global verfügbar. AbortSignal.timeout() seit Node.js 17.3. Natives fetch() mit Signal-Unterstützung seit Node.js 18.
10AbortController vs. ignore-Flag in React?
ignore-Flag verhindert State-Updates, stoppt aber nicht den Netzwerk-Request. Der Server verarbeitet trotzdem. Bei POST/PUT ist das gefährlich. AbortController bricht auf Netzwerkebene ab.