Back

Abbrechen von laufenden Fetch-Anfragen mit AbortController

Abbrechen von laufenden Fetch-Anfragen mit AbortController

Moderne Webanwendungen stellen häufig HTTP-Anfragen, die Benutzer möglicherweise nicht vollständig abschließen müssen. Ein Benutzer, der in ein Suchfeld tippt, generiert mehrere Anfragen, aber nur die neueste ist relevant. Ein Benutzer, der von einer Seite wegnavigiert, macht alle ausstehenden Anfragen für diese Seite irrelevant. Ohne ordnungsgemäße Stornierung verschwenden diese unnötigen Anfragen Bandbreite, verbrauchen Serverressourcen und können dazu führen, dass veraltete Daten in Ihrer Benutzeroberfläche erscheinen.

Die AbortController-API bietet eine saubere, standardisierte Möglichkeit, Fetch-Anfragen und andere asynchrone Operationen abzubrechen. Dieser Artikel zeigt Ihnen, wie Sie die Anfragenstornierung mit AbortController implementieren, und behandelt praktische Muster wie Such-Debouncing, Komponenten-Cleanup und Timeout-Behandlung.

Wichtige Erkenntnisse

  • AbortController erstellt ein Signal, das fetch auf Stornierungsereignisse überwacht
  • Prüfen Sie immer auf AbortError in catch-Blöcken, um Stornierungen von Fehlern zu unterscheiden
  • Brechen Sie vorherige Anfragen in Suchschnittstellen ab, um Race Conditions zu verhindern
  • Verwenden Sie Cleanup-Funktionen in Framework-Komponenten, um Anfragen beim Unmount abzubrechen
  • Kombinieren Sie AbortController mit setTimeout für Request-Timeout-Funktionalität
  • Jeder AbortController ist nur einmal verwendbar - erstellen Sie neue für Retry-Logik
  • Node.js 18+ enthält native AbortController-Unterstützung

Was AbortController und AbortSignal bewirken

AbortController erstellt ein Controller-Objekt, das die Stornierung über ein zugehöriges AbortSignal verwaltet. Der Controller hat eine Aufgabe: abort() aufrufen, wenn Sie eine Operation abbrechen möchten. Das Signal fungiert als Kommunikationskanal, den fetch und andere APIs auf Stornierungsereignisse überwachen.

const controller = new AbortController()
const signal = controller.signal

// Das Signal beginnt als nicht abgebrochen
console.log(signal.aborted) // false

// Der Aufruf von abort() ändert den Zustand des Signals
controller.abort()
console.log(signal.aborted) // true

Wenn Sie ein Signal an fetch übergeben und später abort() aufrufen, wird das fetch-Promise mit einer DOMException namens AbortError abgelehnt. Dies ermöglicht es Ihnen, zwischen Stornierungen und tatsächlichen Netzwerkfehlern zu unterscheiden.

Grundlegende AbortController-Implementierung

Um eine Fetch-Anfrage abzubrechen, erstellen Sie einen AbortController, übergeben sein Signal an fetch und rufen dann abort() auf, wenn nötig:

const controller = new AbortController()

fetch('/api/data', { signal: controller.signal })
  .then(response => response.json())
  .then(data => console.log('Daten empfangen:', data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Anfrage wurde abgebrochen')
    } else {
      console.error('Anfrage fehlgeschlagen:', error)
    }
  })

// Anfrage abbrechen
controller.abort()

Die wichtigsten Punkte:

  • Erstellen Sie den Controller vor der Anfrage
  • Übergeben Sie controller.signal an die fetch-Optionen
  • Rufen Sie controller.abort() zum Abbrechen auf
  • Prüfen Sie auf AbortError in Ihrem catch-Block

Wie man Suchanfragen abbricht, wenn Benutzer tippen

Suchschnittstellen lösen oft Anfragen bei jedem Tastendruck aus. Ohne Stornierung können langsame Antworten in falscher Reihenfolge ankommen und Ergebnisse für veraltete Abfragen anzeigen. So brechen Sie vorherige Suchanfragen ab:

let searchController = null

function performSearch(query) {
  // Bestehende Suche abbrechen
  if (searchController) {
    searchController.abort()
  }

  // Neuen Controller für diese Suche erstellen
  searchController = new AbortController()

  fetch(`/api/search?q=${encodeURIComponent(query)}`, {
    signal: searchController.signal
  })
    .then(response => response.json())
    .then(results => {
      console.log('Suchergebnisse:', results)
      updateSearchUI(results)
    })
    .catch(error => {
      if (error.name === 'AbortError') {
        console.log('Suche abgebrochen')
      } else {
        console.error('Suche fehlgeschlagen:', error)
        showSearchError()
      }
    })
}

// Verwendung: jede neue Suche bricht die vorherige ab
performSearch('javascript')
performSearch('javascript frameworks') // Bricht 'javascript'-Suche ab

Dieses Muster stellt sicher, dass nur die neuesten Suchergebnisse in Ihrer Benutzeroberfläche erscheinen und verhindert Race Conditions, bei denen langsamere Anfragen schnellere überschreiben.

Anfragen beim Component Unmount abbrechen

In Frontend-Frameworks stellen Komponenten oft Fetch-Anfragen, die abgebrochen werden sollten, wenn die Komponente unmounted wird. Dies verhindert Memory Leaks und Fehler beim Versuch, unmounted Komponenten zu aktualisieren.

React-Beispiel

import { useEffect, useState } from 'react'

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const controller = new AbortController()

    async function fetchUser() {
      try {
        const response = await fetch(`/api/users/${userId}`, {
          signal: controller.signal
        })
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`)
        }
        
        const userData = await response.json()
        setUser(userData)
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Benutzer laden fehlgeschlagen:', error)
        }
      } finally {
        if (!controller.signal.aborted) {
          setLoading(false)
        }
      }
    }

    fetchUser()

    // Cleanup-Funktion bricht die Anfrage ab
    return () => controller.abort()
  }, [userId])

  if (loading) return <div>Lädt...</div>
  return <div>{user?.name}</div>
}

Vue-Beispiel

export default {
  data() {
    return {
      user: null,
      controller: null
    }
  },
  async mounted() {
    this.controller = new AbortController()
    
    try {
      const response = await fetch(`/api/users/${this.userId}`, {
        signal: this.controller.signal
      })
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }
      
      this.user = await response.json()
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('Benutzer laden fehlgeschlagen:', error)
      }
    }
  },
  beforeUnmount() {
    if (this.controller) {
      this.controller.abort()
    }
  }
}

Request-Timeouts mit AbortController implementieren

Netzwerkanfragen können unbegrenzt hängen. AbortController kombiniert mit setTimeout bietet einen sauberen Timeout-Mechanismus:

function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
  const controller = new AbortController()
  
  const timeoutId = setTimeout(() => {
    controller.abort()
  }, timeoutMs)

  return fetch(url, {
    ...options,
    signal: controller.signal
  }).finally(() => {
    clearTimeout(timeoutId)
  })
}

// Verwendung
fetchWithTimeout('/api/slow-endpoint', {}, 3000)
  .then(response => response.json())
  .then(data => console.log('Daten:', data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Anfrage-Timeout erreicht')
    } else {
      console.error('Anfrage fehlgeschlagen:', error)
    }
  })

Für wiederverwendbare Timeout-Logik erstellen Sie eine Utility-Funktion:

function createTimeoutSignal(timeoutMs) {
  const controller = new AbortController()
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
  
  // Timeout bereinigen, wenn Signal verwendet wird
  controller.signal.addEventListener('abort', () => {
    clearTimeout(timeoutId)
  }, { once: true })
  
  return controller.signal
}

// Verwendung
fetch('/api/data', { signal: createTimeoutSignal(5000) })
  .then(response => response.json())
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Anfrage-Timeout erreicht oder wurde abgebrochen')
    }
  })

Ordnungsgemäße Fehlerbehandlung für abgebrochene Anfragen

Unterscheiden Sie immer zwischen Stornierungen und echten Fehlern. AbortError zeigt eine absichtliche Stornierung an, nicht einen Fehler:

async function handleRequest(url) {
  const controller = new AbortController()
  
  try {
    const response = await fetch(url, { signal: controller.signal })
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`)
    }
    
    return await response.json()
  } catch (error) {
    if (error.name === 'AbortError') {
      // Dies ist erwartet, wenn wir abbrechen - nicht als Fehler protokollieren
      console.log('Anfrage wurde abgebrochen')
      return null
    }
    
    // Dies ist ein tatsächlicher Fehler, der behandelt werden muss
    console.error('Anfrage fehlgeschlagen:', error)
    throw error
  }
}

Für Anwendungen mit Fehler-Tracking schließen Sie AbortError von Fehlerberichten aus:

.catch(error => {
  if (error.name === 'AbortError') {
    // Stornierungen nicht an Fehler-Tracking melden
    return
  }
  
  // Tatsächliche Fehler melden
  errorTracker.captureException(error)
  throw error
})

Verwaltung mehrerer Anfragen

Beim Umgang mit mehreren gleichzeitigen Anfragen müssen Sie möglicherweise alle auf einmal abbrechen oder sie einzeln verwalten:

class RequestManager {
  constructor() {
    this.controllers = new Map()
  }
  
  async fetch(key, url, options = {}) {
    // Bestehende Anfrage mit demselben Schlüssel abbrechen
    this.cancel(key)
    
    const controller = new AbortController()
    this.controllers.set(key, controller)
    
    try {
      const response = await fetch(url, {
        ...options,
        signal: controller.signal
      })
      return response
    } finally {
      this.controllers.delete(key)
    }
  }
  
  cancel(key) {
    const controller = this.controllers.get(key)
    if (controller) {
      controller.abort()
      this.controllers.delete(key)
    }
  }
  
  cancelAll() {
    for (const controller of this.controllers.values()) {
      controller.abort()
    }
    this.controllers.clear()
  }
}

// Verwendung
const requestManager = new RequestManager()

// Diese Anfragen können unabhängig verwaltet werden
requestManager.fetch('user-profile', '/api/user/123')
requestManager.fetch('user-posts', '/api/user/123/posts')

// Spezifische Anfrage abbrechen
requestManager.cancel('user-profile')

// Alle ausstehenden Anfragen abbrechen
requestManager.cancelAll()

Browser-Support und Node.js-Kompatibilität

AbortController hat ausgezeichnete Browser-Unterstützung:

  • Chrome 66+
  • Firefox 57+
  • Safari 12.1+
  • Edge 16+

Für Node.js ist AbortController nativ in Node 18+ verfügbar. Für frühere Versionen verwenden Sie das abort-controller Polyfill:

npm install abort-controller
// Für Node.js < 18
const { AbortController } = require('abort-controller')

// Normal verwenden
const controller = new AbortController()

Fazit

AbortController bietet eine saubere, standardisierte Möglichkeit, Fetch-Anfragen in modernen Webanwendungen abzubrechen. Die wichtigsten Muster sind: Erstellen Sie einen Controller vor jeder Anfrage, übergeben Sie sein Signal an fetch und rufen Sie abort() auf, wenn eine Stornierung erforderlich ist. Behandeln Sie AbortError immer getrennt von echten Netzwerkfehlern, um zu vermeiden, dass absichtliche Stornierungen als Fehler behandelt werden.

Die häufigsten Anwendungsfälle - Suchanfragen-Management, Komponenten-Cleanup und Timeout-Behandlung - folgen einfachen Mustern, die Sie an Ihre spezifischen Bedürfnisse anpassen können. Mit ordnungsgemäßer Implementierung verbessert die Anfragenstornierung sowohl die Benutzererfahrung als auch die Anwendungsleistung, indem sie unnötige Netzwerkaktivität und veraltete Datenaktualisierungen verhindert.

Bereit, Anfragenstornierung in Ihrer Anwendung zu implementieren? Beginnen Sie mit dem grundlegenden Muster für Ihren häufigsten Anwendungsfall und erweitern Sie dann zu komplexeren Szenarien nach Bedarf. Die Investition in ordnungsgemäßes Request-Management zahlt sich in Anwendungsleistung und Benutzererfahrung aus.

Häufig gestellte Fragen

Nein, sobald abort() aufgerufen wird, bleibt das Signal des Controllers dauerhaft im abgebrochenen Zustand. Sie müssen einen neuen AbortController für jede neue Anfrage oder jeden Wiederholungsversuch erstellen.

Nein, abort() verhindert nur, dass Ihr JavaScript-Code die Antwort verarbeitet. Wenn die Anfrage bereits über das Netzwerk gesendet wurde, kann der Server sie trotzdem empfangen und verarbeiten.

Erstellen Sie mehrere Controller und speichern Sie sie in einem Array oder einer Map, dann rufen Sie abort() auf jedem auf. Alternativ verwenden Sie einen einzigen Controller und übergeben sein Signal an mehrere Fetch-Aufrufe - ein einmaliger abort()-Aufruf bricht alle ab.

Nichts passiert. Der abort()-Aufruf wird ignoriert, wenn die Anfrage bereits erfolgreich abgeschlossen oder aus anderen Gründen fehlgeschlagen ist.

Ja, AbortController funktioniert identisch sowohl mit Promise-Ketten als auch mit async/await. Das fetch-Promise wird mit einem AbortError abgelehnt, wenn es abgebrochen wird, was Sie mit try/catch-Blöcken abfangen können.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers