Fortschrittsanzeigen zum Abrufen hochladen?

99

Ich habe Probleme, Dokumentation oder Beispiele für die Implementierung einer Upload-Fortschrittsanzeige mithilfe von Fetch zu finden .

Dies ist die einzige Referenz, die ich bisher gefunden habe und die besagt:

Fortschrittsereignisse sind eine übergeordnete Funktion, die vorerst nicht abgerufen werden kann. Sie können Ihre eigenen erstellen, indem Sie sich den Content-LengthHeader ansehen und einen Pass-Through-Stream verwenden, um die empfangenen Bytes zu überwachen.

Dies bedeutet, dass Sie Antworten explizit ohne eine Content-Lengthandere behandeln können. Und selbst wenn Content-Lengthes da ist, kann es natürlich eine Lüge sein. Mit Streams können Sie mit diesen Lügen umgehen, wie Sie wollen.

Wie würde ich "einen Pass-Through-Stream zur Überwachung der gesendeten Bytes" schreiben? Wenn es einen Unterschied macht, versuche ich dies, um das Hochladen von Bildern vom Browser auf Cloudinary zu ermöglichen .

HINWEIS : Die Cloudinary JS-Bibliothek interessiert mich nicht , da sie von jQuery abhängt und meine App nicht. Ich interessiere mich nur für die Stream-Verarbeitung, die dazu mit nativem Javascript und Githubs Polyfill erforderlich ist .fetch


https://fetch.spec.whatwg.org/#fetch-api

neezer
quelle

Antworten:

44

Streams landen allmählich auf der Webplattform ( https://jakearchibald.com/2016/streams-ftw/ ), aber es ist noch früh.

Bald können Sie einen Stream als Hauptteil einer Anfrage bereitstellen, aber die offene Frage ist, ob sich der Verbrauch dieses Streams auf hochgeladene Bytes bezieht.

Bestimmte Weiterleitungen können dazu führen, dass Daten erneut an den neuen Speicherort übertragen werden, Streams können jedoch nicht "neu gestartet" werden. Wir können dies beheben, indem wir den Body in einen Rückruf verwandeln, der mehrmals aufgerufen werden kann. Wir müssen jedoch sicherstellen, dass das Offenlegen der Anzahl der Weiterleitungen kein Sicherheitsleck darstellt, da dies das erste Mal auf der Plattform JS sein könnte erkenne das.

Einige fragen sich, ob es überhaupt Sinn macht, den Stream-Verbrauch mit hochgeladenen Bytes zu verknüpfen.

Lange Rede, kurzer Sinn: Dies ist noch nicht möglich, aber in Zukunft wird dies entweder durch Streams oder durch eine Art Rückruf auf höherer Ebene erledigt fetch().

JaffaTheCake
quelle
7
Schade. Akzeptiere dies vorerst, aber wenn dies Realität wird, hoffe ich, dass jemand anderes eine aktualisierte Lösung veröffentlicht! :)
Neezer
@ Jaffa-the-Cake Neuigkeiten?
mu3
1
Update - zeigt den Fortschritt mit der Abruf-API unter Verwendung von Streams - twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer
2
@EitanPeer Schön. Funktioniert etwas Ähnliches für das Hochladen, z. B. POST?
Michael
4
@EitanPeer Aber die Frage ist über den Fortschritt beim Hochladen, nicht beim Herunterladen
John Balvin Arias
23

Meine Lösung besteht darin, Axios zu verwenden , was dies ziemlich gut unterstützt:

      axios.request( {
        method: "post", 
        url: "/aaa", 
        data: myData, 
        onUploadProgress: (p) => {
          console.log(p); 
          //this.setState({
            //fileprogress: p.loaded / p.total
          //})
        }


      }).then (data => {
        //this.setState({
          //fileprogress: 1.0,
        //})
      })

Ich habe ein Beispiel dafür, wie man dies auf Github reagiert .

Dwjohnston
quelle
2
Das war auch meine Lösung. Axios scheint wirklich gut in die Form zu passen.
Jason Rice
1
Hat axiosVerwendung fetchoder XMLHttpRequestunter der Motorhaube?
Dai
3
XMLHttpRequest. Wenn Sie dies für native Reaktionen verwenden, achten Sie darauf, dass XMLHttpRequest im Vergleich zum Abrufen SEHR SEHR langsam ist, um große JSON-Antworten zu analysieren (etwa 10-mal langsamer, und der gesamte UI-Thread friert ein).
Cristiano Coelho
21
Beantwortet die Frage nicht! Wenn die Frage lautet "Wie machst du x in y?" "mach stattdessen x in z" ist keine akzeptable Antwort.
Derek Henderson
3
Dies beantwortet die Frage nicht, insbesondere weil axioses nicht fetchunter der Haube verwendet wird und keine solche Unterstützung hat. Ich schreibe es jetzt buchstäblich für sie so.
Sgammon
7

Ich denke nicht, dass es möglich ist. Der Entwurf besagt:

es fehlt derzeit [ im Vergleich zu XHR ], wenn es um das Fortschreiten von Anfragen geht


(alte Antwort):
Das erste Beispiel im Kapitel Fetch API gibt einen Einblick in die Vorgehensweise :

Wenn Sie die Körperdaten schrittweise erhalten möchten:

function consume(reader) {
  var total = 0
  return new Promise((resolve, reject) => {
    function pump() {
      reader.read().then(({done, value}) => {
        if (done) {
          resolve()
          return
        }
        total += value.byteLength
        log(`received ${value.byteLength} bytes (${total} bytes in total)`)
        pump()
      }).catch(reject)
    }
    pump()
  })
}

fetch("/music/pk/altes-kamuffel.flac")
  .then(res => consume(res.body.getReader()))
  .then(() => log("consumed the entire body without keeping the whole thing in memory!"))
  .catch(e => log("something went wrong: " + e))

Abgesehen von der Verwendung des PromiseKonstruktor-Antimusters können Sie sehen, dass response.bodyes sich um einen Stream handelt, aus dem Sie mit einem Reader Byte für Byte lesen können, und Sie können für jeden von ihnen ein Ereignis auslösen oder tun, was Sie möchten (z. B. den Fortschritt protokollieren).

Die Streams-Spezifikation scheint jedoch noch nicht ganz fertig zu sein, und ich habe keine Ahnung, ob dies bereits in einer Abrufimplementierung funktioniert.

Bergi
quelle
11
Wenn ich dieses Beispiel richtig lese, dient dies zum Herunterladen einer Datei über fetch. Ich interessiere mich für Fortschrittsanzeigen zum Hochladen einer Datei.
Neezer
Hoppla, dieses Zitat spricht vom Empfangen von Bytes, was mich verwirrte.
Bergi
@Bergi Hinweis, PromiseKonstruktor ist nicht erforderlich. Response.body.getReader()gibt a zurück Promise. Siehe So lösen Sie Uncaught RangeError beim Herunterladen von json
guest271314
3
@ guest271314 Ja, ich habe es bereits an der Quelle des Zitats behoben . Und nein, getReadergibt kein Versprechen zurück. Keine Ahnung, was dies mit dem von Ihnen verlinkten Beitrag zu tun hat.
Bergi
@Bergi Ja, Sie sind korrekt. .getReader()Die .read()Methode gibt a zurück Promise. Das wollte ich vermitteln. Der Link soll auf die Prämisse verweisen, dass, wenn der Fortschritt zum Herunterladen überprüft werden kann, der Fortschritt zum Hochladen überprüft werden kann. Stellen Sie ein Muster zusammen, das das erwartete Ergebnis in nennenswertem Maße zurückgibt. Das ist ein Fortschritt beim fetch()Hochladen. Habe bei jsfiddle keinen Weg zu echoeinem Bloboder einem FileObjekt gefunden, wahrscheinlich fehlt etwas Einfaches. Testen bei localhostUpload-Dateien sehr schnell, ohne die Netzwerkbedingungen nachzuahmen; obwohl gerade erinnert Network throttling.
guest271314
6

Update: Wie die akzeptierte Antwort sagt, ist es jetzt unmöglich. Aber der folgende Code hat unser Problem für einige Zeit gelöst. Ich sollte hinzufügen, dass wir zumindest auf eine Bibliothek umsteigen mussten, die auf XMLHttpRequest basiert.

const response = await fetch(url);
const total = Number(response.headers.get('content-length'));

const reader = response.body.getReader();
let bytesReceived = 0;
while (true) {
    const result = await reader.read();
    if (result.done) {
        console.log('Fetch complete');
        break;
    }
    bytesReceived += result.value.length;
    console.log('Received', bytesReceived, 'bytes of data so far');
}

Dank diesem Link: https://jakearchibald.com/2016/streams-ftw/

Hosseinmp76
quelle
2
Schön, aber gilt das auch für Uploads?
Kernel
@kernel Ich habe versucht es herauszufinden, aber ich konnte es nicht tun. und ich finde gerne einen Weg, dies auch für den Upload zu tun.
Hosseinmp76
Das Gleiche, aber bisher hatte ich nicht allzu viel Glück, ein funktionierendes Upload-Beispiel zu finden / zu erstellen.
Kernel
1
content-length! == Körperlänge. Wenn die http-Komprimierung verwendet wird (häufig bei großen Downloads), entspricht die Inhaltslänge der Größe nach der http-Komprimierung, während die Länge der Größe nach dem Extrahieren der Datei entspricht.
Ferrybig
@Ferrybig Ich habe deinen Standpunkt nicht verstanden. Habe ich die Gleichheit irgendwo benutzt?
Hosseinmp76
4

Da keine der Antworten das Problem löst.

Nur zur Implementierung können Sie die Upload-Geschwindigkeit mit einem kleinen Anfangsblock bekannter Größe ermitteln und die Upload-Zeit mit der Länge des Inhalts / der Upload-Geschwindigkeit berechnen. Sie können diese Zeit als Schätzung verwenden.

Shishir Arora
quelle
3
Sehr cleverer, netter Trick, während wir auf eine Echtzeitlösung warten :)
Magix
13
Zu riskant für mich. Ich möchte nicht wie die Windows-Kopierdatei-Fortschrittsanzeige enden
Jack Giffin
2

Eine mögliche Problemumgehung wäre, den new Request()Konstruktor zu verwenden und dann das Request.bodyUsed BooleanAttribut zu überprüfen

Der bodyUsedGetter des Attributs muss true if disturbedund andernfalls false zurückgeben.

um festzustellen, ob Stream ist distributed

Ein Objekt, das das BodyMixin implementiert, heißt disturbedif , wenn bodyes nicht null ist und es streamist disturbed.

Geben Sie den fetch() Promisevon innen .then()verketteten bis rekursiven .read()Aufruf eines ReadableStreamWann Request.bodyUsedgleich gleich zurück true.

Beachten Sie, dass der Ansatz die Bytes von nicht liest, Request.bodywenn die Bytes zum Endpunkt gestreamt werden. Außerdem kann der Upload abgeschlossen sein, bevor eine Antwort vollständig an den Browser zurückgegeben wird.

const [input, progress, label] = [
  document.querySelector("input")
  , document.querySelector("progress")
  , document.querySelector("label")
];

const url = "/path/to/server/";

input.onmousedown = () => {
  label.innerHTML = "";
  progress.value = "0"
};

input.onchange = (event) => {

  const file = event.target.files[0];
  const filename = file.name;
  progress.max = file.size;

  const request = new Request(url, {
    method: "POST",
    body: file,
    cache: "no-store"
  });

  const upload = settings => fetch(settings);

  const uploadProgress = new ReadableStream({
    start(controller) {
        console.log("starting upload, request.bodyUsed:", request.bodyUsed);
        controller.enqueue(request.bodyUsed);
    },
    pull(controller) {
      if (request.bodyUsed) {
        controller.close();
      }
      controller.enqueue(request.bodyUsed);
      console.log("pull, request.bodyUsed:", request.bodyUsed);
    },
    cancel(reason) {
      console.log(reason);
    }
  });

  const [fileUpload, reader] = [
    upload(request)
    .catch(e => {
      reader.cancel();
      throw e
    })
    , uploadProgress.getReader()
  ];

  const processUploadRequest = ({value, done}) => {
    if (value || done) {
      console.log("upload complete, request.bodyUsed:", request.bodyUsed);
      // set `progress.value` to `progress.max` here 
      // if not awaiting server response
      // progress.value = progress.max;
      return reader.closed.then(() => fileUpload);
    }
    console.log("upload progress:", value);
    progress.value = +progress.value + 1;
    return reader.read().then(result => processUploadRequest(result));
  };

  reader.read().then(({value, done}) => processUploadRequest({value,done}))
  .then(response => response.text())
  .then(text => {
    console.log("response:", text);
    progress.value = progress.max;
    input.value = "";
  })
  .catch(err => console.log("upload error:", err));

}
guest271314
quelle
-3
const req = await fetch('./foo.json');
const total = Number(req.headers.get('content-length'));
let loaded = 0;
for await(const {length} of req.body.getReader()) {
  loaded = += length;
  const progress = ((loaded / total) * 100).toFixed(2); // toFixed(2) means two digits after floating point
  console.log(`${progress}%`); // or yourDiv.textContent = `${progress}%`;
}
Leon Gilyadov
quelle
Ich möchte Benjamin Gruenbaum für die ganze Antwort danken. Weil ich es aus seinem Vortrag gelernt habe.
Leon Gilyadov
@LeonGilyadov Ist die Vorlesung überall online verfügbar? Ein Link zur Quelle wäre schön.
Mark Amery
@ MarkAmery Hier ist es: youtube.com/watch?v=Ja8GKkxahCo (der Vortrag wurde auf Hebräisch gehalten)
Leon Gilyadov
10
Die Frage bezieht sich auf das Hochladen, nicht auf das Herunterladen.
Sarneeh
Das Problem mit dem Abruffortschritt ist, wenn Sie hochladen möchten (es gibt kein Problem mit dem Download)
Kamil Kiełczewski
-4

Schlüsselteil ist ReadableStream obj_response .body).

Stichprobe:

let parse=_/*result*/=>{
  console.log(_)
  //...
  return /*cont?*/_.value?true:false
}

fetch('').
then(_=>( a/*!*/=_.body.getReader(), b/*!*/=z=>a.read().then(parse).then(_=>(_?b:z=>z)()), b() ))

Sie können es auf einer großen Seite testen, z. B. https://html.spec.whatwg.org/ und . https://html.spec.whatwg.org/print.pdf . CtrlShiftJ und laden Sie den Code in.

(Auf Chrome getestet.)

Pacerier
quelle
Diese Antwort erhält Minuspunkte, aber niemand erklärt, warum Minuspunkte vergeben werden - also gebe ich +1
Kamil Kiełczewski
2
Es bekommt eine -1 von mir, weil es für das Hochladen nicht relevant ist .
Brad