Asynchrone Operationen mit abbrechbarem Fetch in JavaScript ermöglichen
Emily Parker
Product Engineer · Leapcell

Einleitung
In der modernen Webentwicklung sind asynchrone Operationen der Eckpfeiler dynamischer und reaktionsschneller Anwendungen. Vom Abrufen von Daten von einem Server bis zum Laden externer Ressourcen beschäftigen sich Entwickler ständig mit Aufgaben, deren Abschluss eine unvorhersehbare Zeit dauern kann. Während Promises und async/await die Art und Weise, wie wir diese Operationen handhaben, dramatisch verbessert haben, bleibt eine häufige Herausforderung bestehen: Was passiert, wenn eine laufende asynchrone Aufgabe nicht mehr benötigt wird? Möglicherweise navigiert ein Benutzer von einer Seite weg, oder eine Suchanfrage ändert sich schnell, wodurch die vorherige Netzwerkanfrage obsolet wird. Ohne einen Mechanismus zum Abbrechen dieser Operationen riskieren wir, wertvolle Netzwerkbandbreite zu verschwenden, unnötige Serverressourcen zu verbrauchen und möglicherweise Race Conditions oder Speicherlecks in unseren Anwendungen einzuführen. Dieser Artikel befasst sich mit dem kritischen Konzept abbrechbarer asynchroner Operationen, konzentriert sich speziell auf die fetch-API und zeigt, wie diese Funktionalität konsistent sowohl in clientseitigen (Browser) als auch in serverseitigen (Node.js) JavaScript-Umgebungen implementiert werden kann.
Kernkonzepte für abbrechbares Fetch
Bevor wir uns mit den Implementierungsdetails befassen, lassen Sie uns einige Kernkonzepte klären, die für abbrechbare fetch-Operationen zentral sind.
Asynchrone Operationen
Im Grunde ist eine asynchrone Operation eine Aufgabe, die unabhängig vom Hauptprogrammablauf ausgeführt werden kann und es dem Programm ermöglicht, andere Aufgaben auszuführen, während es auf den Abschluss der asynchronen Aufgabe wartet. In JavaScript wird dies hauptsächlich über die Event-Schleife, Promises und die async/await-Syntax verwaltet. fetch ist ein Paradebeispiel für eine asynchrone Operation, da es ein Promise zurückgibt, das schließlich mit einem Response-Objekt aufgelöst oder mit einem Fehler abgelehnt wird.
Die Fetch-API
Die fetch-API bietet eine leistungsstarke und flexible Schnittstelle zum Abrufen von Ressourcen über das Netzwerk. Sie ist ein moderner Ersatz für XMLHttpRequest und bietet einen robusteren und Promise-basierten Ansatz für HTTP-Anfragen. Ihre Einfachheit und Leistung haben sie zur bevorzugten Wahl für Webentwickler gemacht.
AbortController und AbortSignal
Die AbortController-Schnittstelle ist eine entscheidende Komponente, um fetch-Anfragen abbrechbar zu machen. Sie bietet eine Möglichkeit, ein oder mehrere DOM-Anfragen nach Bedarf zu signalisieren und abzubrechen. Einem AbortController-Instanz ist ein zugehöriges AbortSignal-Objekt zugeordnet. Dieses AbortSignal kann an die fetch-API (und andere asynchrone APIs) übergeben werden, um Abbruchsignale zu beobachten und darauf zu reagieren. Wenn die Methode abort() auf der AbortController-Instanz aufgerufen wird, sendet sie ein abort-Ereignis aus, und jede fetch-Anfrage, die auf dieses AbortSignal hört, wird abgebrochen, wodurch ihr Promise mit einem AbortError abgelehnt wird.
Implementierung von abbrechbarem Fetch im Browser
Die AbortController-API wird in modernen Browsern nativ unterstützt, was die Implementierung von abbrechbaren fetch-Anfragen auf der Client-Seite vereinfacht.
Lassen Sie uns dies anhand eines praktischen Beispiels veranschaulichen: das Abrufen von Benutzerdaten, die abgebrochen werden könnten, wenn der Benutzer navigiert oder eine neue Suchanfrage eingibt.
// Funktion zum Abrufen von Benutzerdaten mit einem Abbruchmechanismus async function fetchUserData(userId, signal) { try { const response = await fetch(`https://api.example.com/users/${userId}`, { signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('User data:', data); return data; } catch (error) { if (error.name === 'AbortError') { console.log('Fetch request for user data was aborted.'); } else { console.error('Error fetching user data:', error); } throw error; // Erneut auslösen für weitere Verarbeitung, falls erforderlich } } // Beispielverwendung const controller = new AbortController(); const signal = controller.signal; // Simulieren des Initiierens eines Fetches durch einen Benutzer const promise = fetchUserData(123, signal); // Simulieren der Navigation des Benutzers oder einer neuen Anfrage nach 2 Sekunden setTimeout(() => { console.log('Aborting fetch request...'); controller.abort(); }, 2000); promise .then(data => { console.log('Successfully fetched:', data); }) .catch(error => { // AbortError wird innerhalb von fetchUserData behandelt, aber andere Fehler können hier propagieren if (error.name !== 'AbortError') { console.error('Operation failed:', error); } }); // Weiteres Beispiel: Eine neue Fetch-Anfrage für einen anderen Benutzer, die die alte potenziell abbricht // In einer realen Anwendung würden Sie möglicherweise einen einzelnen Controller für eine bestimmte Komponente verwalten // und jedes Mal einen neuen erstellen, wenn eine neue Anfrage initiiert wird, falls die alte abgebrochen werden soll. const searchInput = document.getElementById('search-input'); // Stellen Sie sich ein solches Element vor let currentController = null; searchInput?.addEventListener('input', (event) => { if (currentController) { currentController.abort(); // Vorherige Anfrage abbrechen } currentController = new AbortController(); const newSignal = currentController.signal; const searchTerm = event.target.value; if (searchTerm.length > 2) { // Nur suchen, wenn mehr als 2 Zeichen vorhanden sind fetchUserData(`search?q=${searchTerm}`, newSignal) .then(results => console.log('Search results:', results)) .catch(error => { if (error.name !== 'AbortError') { console.error('Search failed:', error); } }); } });
In diesem Browser-Beispiel erstellen wir einen AbortController, extrahieren sein signal und übergeben es an den fetch-Aufruf. Später, wenn controller.abort() aufgerufen wird, wird das fetch-Promise mit einem AbortError abgelehnt, den wir dann spezifisch abfangen und behandeln. Dieses Muster ist unerlässlich, um erwartete Abbrüche von echten Netzwerkfehlern zu filtern.
Implementierung von abbrechbarem Fetch in Node.js
Historisch gesehen hatte Node.js keine native fetch-API. Entwickler griffen typischerweise auf Drittanbieterbibliotheken wie node-fetch oder axios zurück. Mit Node.js v18 und höher ist die fetch-API jetzt global verfügbar und integriert, was eine dringend benötigte Konsistenz mit der Browserumgebung bringt. Entscheidend ist, dass diese integrierte fetch-API auch AbortController unterstützt.
Lassen Sie uns unser vorheriges Beispiel für eine Node.js-Umgebung anpassen.
// Node.js-spezifisches globales Fetch ist ab Node.js v18+ verfügbar // Wenn Sie eine ältere Version verwenden, müssten Sie `npm install node-fetch` installieren und `import fetch from 'node-fetch';` // und sicherstellen, dass AbortController ebenfalls verfügbar ist (z. B. aus dem npm-Paket 'abort-controller'). // Funktion zum Abrufen von Benutzerdaten mit einem Abbruchmechanismus async function fetchUserDataNode(userId, signal) { try { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, { signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('User data from Node.js:', data); return data; } catch (error) { if (error.name === 'AbortError') { console.log('Node.js fetch request for user data was aborted.'); } else { console.error('Error fetching user data from Node.js:', error); } throw error; } } // Beispielverwendung in Node.js const controllerNode = new AbortController(); const signalNode = controllerNode.signal; console.log('Initiating Node.js fetch...'); const promiseNode = fetchUserDataNode(1, signalNode); // Simulieren einer Bereinigung oder eines Timeouts in Node.js setTimeout(() => { console.log('Aborting Node.js fetch request...'); controllerNode.abort(); }, 1500); // Nach 1,5 Sekunden abbrechen promiseNode .then(data => { console.log('Node.js successfully fetched:', data); }) .catch(error => { if (error.name !== 'AbortError') { console.error('Node.js operation failed:', error); } }); // Weiteres Beispiel: Eine Anfrage, die vor dem Abbruch abgeschlossen wird const controllerWillComplete = new AbortController(); const signalWillComplete = controllerWillComplete.signal; console.log('Initiating a fetch that will complete...'); fetchUserDataNode(2, signalWillComplete) .then(data => console.log('Node.js fetch completed successfully for user 2:', data)) .catch(error => { if (error.name !== 'AbortError') { console.error('Node.js fetch failed for user 2:', error); } }); // Kein abort-Aufruf für controllerWillComplete, daher sollte er abgeschlossen werden.
Die Code-Struktur in Node.js spiegelt die des Browsers wider. Der AbortController und AbortSignal verhalten sich exakt gleich, was einen konsistenten Ansatz für abbrechbare fetch-Operationen in beiden Umgebungen ermöglicht. Diese konsistente plattformübergreifende Unterstützung ist ein bedeutender Vorteil, der die Entwicklung vereinfacht und die kognitive Belastung für Entwickler reduziert, die an Full-Stack-JavaScript-Anwendungen arbeiten.
Anwendungsszenarien
Abbrechbare fetch-Operationen sind in verschiedenen realen Szenarien äußerst nützlich:
- Such-Autovervollständigung/Typeahead: Wenn ein Benutzer schnell tippt, werden frühere Suchanfragen redundant. Deren Abbruch verhindert unnötigen Netzwerkverkehr und stellt sicher, dass nur die aktuellsten relevanten Ergebnisse angezeigt werden, wodurch Race Conditions verhindert werden, bei denen ältere, langsamere Antworten neuere überschreiben könnten.
 - Benutzernavigation: Wenn ein Benutzer von einer Seite oder Komponente weg navigiert, die eine 
fetch-Anfrage initiiert hat, gibt der Abbruch der laufenden Anfrage Ressourcen frei und bereinigt potenzielle Hintergrundprozesse. - Polling/Long-Polling-Bereinigung: Bei der Implementierung von Mechanismen, die periodisch Daten abrufen, ist das Abbrechen des aktuellen Polling vor der Planung des nächsten (z. B. wenn eine Komponente unmountet) für die Ressourcenverwaltung unerlässlich.
 - Bedingungsladeszenarien: Stellen Sie sich ein Dashboard mit mehreren Tabs vor. Wenn ein Benutzer zwischen den Tabs wechselt, möchten Sie möglicherweise Fetches abbrechen, die sich auf den zuvor aktiven Tab beziehen, um das Laden von Daten für den neuen Tab zu priorisieren.
 - Timeouts und Wiederholungsversuche: Während 
AbortControllerhauptsächlich explizite Abbrüche signalisiert, kann er mit Timeout-Mechanismen kombiniert werden. Wenn beispielsweise einfetchzu lange dauert, könnten Sie es automatisch abbrechen. 
Fazit
Die Implementierung abbrechbarer asynchroner Operationen, insbesondere mit der fetch-API, ist eine wichtige Technik für die Erstellung robuster, effizienter und benutzerfreundlicher Webanwendungen. Durch die Nutzung des AbortController- und AbortSignal-Musters können Entwickler Netzwerkaufrufe effektiv verwalten, Ressourcenverschwendung verhindern und die Reaktionsfähigkeit von Anwendungen sowohl in Browser- als auch in Node.js-Umgebungen verbessern. Dieser konsistente Ansatz ermöglicht es Entwicklern, saubereren, wartbareren Code zu schreiben, der die dynamische Natur asynchroner Aufgaben elegant handhabt. Die Fähigkeit, laufende Operationen abzubrechen, ist nicht nur eine Optimierung; sie ist ein grundlegender Aspekt der modernen asynchronen Programmierung.