Signatur
Beschreibung
Mit AbortController lassen sich asynchrone Vorgänge von außen abbrechen. Ein Controller wird mit new AbortController() erzeugt und hat genau zwei Bestandteile: die schreibgeschützte Eigenschaft signal (ein AbortSignal) und die Methode abort(reason). Das Signal gibt man an die Operation weiter, die abbrechbar sein soll. Den Controller behält man selbst, um den Abbruch auszulösen.
Typische Einsatzfälle sind:
- das Abbrechen von
fetch()-Anfragen, z. B. wenn der Nutzer die Seite wechselt oder eine neue Suche startet, - das Abbrechen von Streams und vom Lesen eines Response-Bodys,
- das Entfernen mehrerer Event-Listener auf einmal über die Option
signalvonaddEventListener(), - der Abbruch eigener asynchroner Funktionen, die auf das Signal hören.
Ruft man abort() auf, wechselt signal.aborted auf true, das Signal löst das Ereignis abort aus und signal.reason bekommt den übergebenen Grund. Ohne Grund ist das eine DOMException mit dem Namen "AbortError". Laufende fetch()-Promises werden mit diesem Grund zurückgewiesen.
Ein Controller kann nur einmal abbrechen. Ein bereits abgebrochenes Signal bleibt dauerhaft abgebrochen. Für jeden neuen Vorgang, der sich separat abbrechen lassen soll, braucht man deshalb einen neuen AbortController.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $reason | any | DOMException "AbortError" | Nur bei abort(reason): der Abbruchgrund. Er steht danach in signal.reason und wird zum Grund, mit dem abhängige Promises zurückgewiesen werden. |
Rückgabewert
new AbortController() liefert eine neue Instanz mit frischem, nicht abgebrochenem signal. abort() selbst gibt undefined zurück.Beispiele
Fetch-Anfrage per Button abbrechen
const controller = new AbortController();
const { signal } = controller;
document.querySelector('#cancel').addEventListener('click', () => {
controller.abort();
});
try {
const response = await fetch('/api/grosse-datei', { signal });
const data = await response.json();
console.log('Geladen:', data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Anfrage wurde abgebrochen');
} else {
throw err;
}
}
Veraltete Suchanfragen verwerfen (Typeahead)
let controller = null;
const search = async (query) => {
controller?.abort(); // vorherige Anfrage abbrechen
controller = new AbortController();
try {
const res = await fetch(`/api/suche?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
});
return await res.json();
} catch (err) {
if (err.name === 'AbortError') return null; // überholt
throw err;
}
};
document.querySelector('#q').addEventListener('input', async (e) => {
const result = await search(e.target.value);
if (result) console.log(result);
});
Mehrere Event-Listener auf einmal entfernen
const controller = new AbortController();
const { signal } = controller;
window.addEventListener('resize', () => console.log('resize'), { signal });
window.addEventListener('scroll', () => console.log('scroll'), { signal });
document.addEventListener('keydown', (e) => console.log(e.key), { signal });
// Später, z. B. beim Entfernen einer Komponente:
controller.abort();
console.log(signal.aborted);
Eigene asynchrone Funktion abbrechbar machen, mit eigenem Grund
const wait = (ms, { signal } = {}) =>
new Promise((resolve, reject) => {
if (signal?.aborted) return reject(signal.reason);
const id = setTimeout(resolve, ms, 'fertig');
signal?.addEventListener('abort', () => {
clearTimeout(id);
reject(signal.reason);
}, { once: true });
});
const controller = new AbortController();
setTimeout(() => controller.abort(new Error('Vom Nutzer abgebrochen')), 500);
try {
console.log(await wait(2000, { signal: controller.signal }));
} catch (err) {
console.log(err.message);
}
// Wichtig · Fallstricke
- Nur einmal verwendbar: Nach
abort()bleibt das Signal dauerhaft abgebrochen. Übergibt man es an ein neuesfetch(), wird dieses sofort zurückgewiesen. Für jeden neuen Vorgang also einen neuen Controller anlegen. - Fehler unterscheiden: Ein Abbruch erscheint als abgewiesenes Promise. Prüfe
err.name === 'AbortError', damit Abbrüche nicht als echte Fehler gemeldet werden. Mit eigenemreasonwird genau dieser Wert geworfen, nicht derAbortError. - Kein Rückgängigmachen auf dem Server: Mit
abort()hört der Client auf, auf die Antwort zu warten. Eine Anfrage, die schon beim Server angekommen ist (z. B.POST), wird dort unter Umständen trotzdem vollständig verarbeitet. - Zeitlimits: Für einfache Zeitlimits ist
AbortSignal.timeout(ms)kürzer. Mehrere Signale kombiniert man mitAbortSignal.any([...]). Beide sind neuer und werden von älteren Browsern nicht unterstützt. - Kompatibilität:
AbortControllerist in allen modernen Browsern verfügbar. Der Parameterreasonundsignal.reasonkamen erst um 2022 dazu (Chrome 98, Firefox 97, Safari 15.4). In Node.js istAbortControllerab Version 15 global verfügbar. Der Internet Explorer unterstützt es nicht. - Listener, die man selbst per
signal.addEventListener('abort', …)registriert, sollten die Option{ once: true }nutzen, damit bei langlebigen Signalen keine Speicherlecks entstehen.