W3docs

Javascript Fetch: Download-Fortschritt

Erfahren Sie, wie Sie mit der Fetch API und ReadableStream den Download-Fortschritt in JavaScript verfolgen und eine Fortschrittsanzeige erstellen.

Die Fetch API ist der moderne Weg, Netzwerkanfragen in JavaScript zu stellen, aber await fetch(...) löst sich auf, sobald die Header der Antwort eintreffen — lange bevor der Body vollständig heruntergeladen wurde. Um einen Fortschrittsbalken für eine große Datei anzuzeigen, müssen Sie den Body schrittweise lesen und die eingehenden Bytes zählen. Dieses Kapitel erklärt genau das mithilfe eines ReadableStream, warum Content-Length wichtig ist, wie man eine Fortschritts-UI aufbaut und was fetch nicht kann (Upload-Fortschritt).

Warum fetch einen Stream für den Fortschritt benötigt

Wenn Sie const data = await response.json() schreiben, puffert der Browser den gesamten Body intern und gibt ihn erst zurück, wenn er vollständig ist — es gibt keinen Hook, um die Bytes unterwegs zu beobachten. Der Response-Body ist jedoch als ReadableStream über response.body zugänglich. Indem Sie Chunks selbst aus diesem Stream lesen, können Sie messen, wie viel bereits angekommen ist, und die UI bei jedem Chunk aktualisieren.

Das Rezept ist immer dasselbe:

  1. Einen Reader holen: const reader = response.body.getReader().
  2. In einer Schleife reader.read() aufrufen, das zu { done, value } auflöst — value ist ein Uint8Array-Chunk.
  3. value.length zu einer laufenden Gesamtsumme addieren und den Fortschritt melden.
  4. Die Chunks aufbewahren und sie zusammensetzen, sobald done den Wert true hat.

Den Body als ReadableStream lesen

Um den Prozentsatz zu kennen, benötigen Sie auch die Gesamtgröße. Der Server sollte einen Content-Length-Response-Header senden; lesen Sie ihn mit response.headers.get('Content-Length'). Er fehlt häufig (Chunked Transfer Encoding, gzip-Komprimierung oder ein Server, der ihn schlicht weglässt), daher fällt das folgende Beispiel auf eine vom Aufrufer angegebene Größe zurück.

javascript— editable

Die Funktion fetchWithProgress ruft Daten von einer angegebenen URL ab und verfolgt den Download-Fortschritt. Sie liest den Response-Body in Chunks über das ReadableStream-Interface und ruft den onProgress-Callback mit der empfangenen Länge und der gesamten Content-Länge auf. Die heruntergeladenen Daten werden dann aus den Chunks rekonstruiert und als dekodierter String zurückgegeben.

Hinweis

Die Funktion versucht, den Content-Length-Header automatisch zu lesen. Wenn der Header fehlt (häufig bei Chunked Transfers oder Komprimierung), fällt sie auf den fallbackSize-Parameter zurück. In der Produktion muss der Entwickler oder Server diese Fallback-Größe bereitstellen. Vermeiden Sie bei großen Dateien das Puffern aller Chunks im Speicher; verarbeiten Sie sie schrittweise oder verwenden Sie response.blob() für Nicht-Text-Antworten.

Sobald die Schleife endet, haben Sie ein Array von Uint8Array-Chunks. Um daraus nutzbare Daten zu machen, kopieren Sie sie in einen einzelnen Buffer und dekodieren ihn (für Text) oder verpacken ihn in einem Blob (für Binärdateien, Bilder, Downloads). Lesen Sie JavaScript Blob für die Arbeit mit Binärdaten und das Auslösen von Datei-Downloads.

Fortschritt für Benutzer anzeigen

Ein Fortschrittsbalken ist lediglich ein Element, dessen Breite received / total widerspiegelt. Übergeben Sie einen updateProgressBar-Callback anstelle des Logging-Callbacks:

<body>
  <div id="progress-bar" style="width: 100%; background-color: #e0e0e0;">
    <div id="progress" style="width: 0; height: 20px; background-color: #76c7c0;"></div>
  </div>
  <div id="output"></div>
  <script>
    function updateProgressBar(received, total) {
      const progressElement = document.getElementById('progress');
      const percentage = Math.min(100, (received / total) * 100);
      progressElement.style.width = percentage + '%';
    }
    document.addEventListener('DOMContentLoaded', () => {
      const url = 'https://api.w3docs.com/uploads/media/default/0001/05/dd10c28a7052fb6d2ff13bc403842b797a73ff3b.txt';
      const size = 3_900_000; // fallback size
      // fetchWithProgress is defined in the previous code block
      fetchWithProgress(url, updateProgressBar, size)
      .then(data => {
        document.getElementById('output').textContent = 'File content: ' + data.slice(0, 1000) + '...';
      })
      .catch(err => console.error("Download failed:", err));
    });
  </script>
</body>
Warnung

Wenn Sie eine sehr große Datei herunterladen, sollten Sie vermeiden, die UI zu häufig zu aktualisieren. Anstatt den Fortschrittsbalken für jeden einzelnen Chunk zu aktualisieren, können Sie ihn seltener aktualisieren (z. B. alle paar Chunks oder basierend auf einem Zeitintervall). Das hält Ihre UI leichtgewichtig.

Die HTML-Elemente im obigen Beispiel richten einen Fortschrittsbalken ein, um den Download-Fortschritt zu visualisieren, und einen vorformatierten Textbereich, um den heruntergeladenen Inhalt anzuzeigen. Das progress-bar-div dient als Container und das progress-div stellt den tatsächlichen Fortschritt des Downloads dar.

Der JavaScript-Code aktualisiert den Fortschrittsbalken basierend auf der empfangenen Datenlänge und der gesamten Content-Länge. Die updateProgressBar-Funktion berechnet den Prozentsatz der heruntergeladenen Daten und passt die Breite des Fortschrittsbalkens entsprechend an. Der Event-Listener löst die fetchWithProgress-Funktion beim Laden der Seite aus, aktualisiert den Fortschrittsbalken und zeigt den heruntergeladenen Inhalt im output-Element an.

Hinweis: fetchWithProgress ist im vorherigen Snippet definiert. Stellen Sie in einem echten Projekt sicher, dass es im Gültigkeitsbereich liegt (z. B. über Module-Imports, Bundling oder ein globales Script-Tag).

Was fetch nicht kann: Upload-Fortschritt

Die Stream-Technik verfolgt nur den Download-(Response-)Fortschritt. Derzeit gibt es keine Standardmethode, um den Upload-(Request-Body-)Fortschritt mit fetch zu beobachten — der Request-Body wird in Browsern nicht als beobachtbarer Stream bereitgestellt. Wenn Sie beim Senden einer Datei einen Fortschrittsbalken benötigen, greifen Sie auf XMLHttpRequest zurück, dessen upload-Objekt progress-Events auslöst:

function uploadWithProgress(url, file, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', url);

    // upload.onprogress fires repeatedly while the body is sent
    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        const percent = Math.round((event.loaded / event.total) * 100);
        onProgress(event.loaded, event.total, percent);
      }
    };

    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(new Error('Upload failed'));
    xhr.send(file);
  });
}

event.lengthComputable gibt an, ob event.total bekannt ist; prüfen Sie dies immer, bevor Sie einen Prozentsatz berechnen.

Einen laufenden Download abbrechen

Ein langer Download sollte abbrechbar sein. Übergeben Sie ein AbortSignal an fetch und rufen Sie controller.abort() auf, um sowohl die Anfrage als auch die Stream-Schleife zu stoppen — lesen Sie Fetch: Abort für das vollständige Muster.

Profitipps

  • Binärdaten verarbeiten: TextDecoder funktioniert nur für Text. Verwenden Sie response.blob() oder response.arrayBuffer() für Binärdateien.
  • Fortschritts-Updates drosseln: Schnelle Chunk-Reads können den UI-Thread blockieren. Drosseln oder entprellen Sie den Fortschritts-Callback.
  • Speicherverwaltung: Vermeiden Sie das Speichern aller Chunks in einem Array bei großen Dateien. Verarbeiten Sie sie schrittweise.
  • Content-Length-Fallback: Der Header fehlt häufig. Stellen Sie immer eine zuverlässige fallbackSize bereit.
  • Benutzer-Feedback optimieren: Echtzeit-Feedback zum Download-Fortschritt verbessert die Benutzerzufriedenheit und lässt Ihre Anwendung reaktionsschneller wirken.
  • Browser-Fähigkeiten nutzen: Verschiedene Browser können unterschiedliche Unterstützung für erweiterte Funktionen bieten. Testen Sie Ihre Implementierung in mehreren Browsern, um die Kompatibilität sicherzustellen.
  • Bei Möglichkeit vereinfachen: Für Downloads, bei denen Streaming-Fortschritt nicht unbedingt erforderlich ist, bietet response.arrayBuffer() eine einfachere Möglichkeit, Daten zu rekonstruieren, ohne Chunks manuell zusammenzuführen.

Mit diesen Erkenntnissen und Beispielen sind Sie nun gerüstet, die Fetch API mit Download-Fortschrittsverfolgung in Ihren Projekten zu implementieren und Benutzern ein nahtloses und informatives Erlebnis zu bieten.

Fazit

Die Fetch API vollständig zu beherrschen bedeutet, nicht nur zu verstehen, wie man einfache Anfragen stellt, sondern auch, wie man fortgeschrittene Szenarien wie die Verfolgung des Download-Fortschritts handhabt. Durch die Verwendung von ReadableStreams können wir Downloads überwachen und Feedback geben, was die Benutzererfahrung erheblich verbessert. Die Implementierung dieser Techniken stellt sicher, dass Ihre Anwendungen robust, benutzerfreundlich und in der Lage sind, große Datenübertragungen effizient zu bewältigen.

Übungen

Übung
Was sind die wichtigsten Schritte zur Verfolgung des Download-Fortschritts mit der Fetch API?
Was sind die wichtigsten Schritte zur Verfolgung des Download-Fortschritts mit der Fetch API?
Was this page helpful?