Carica gli indicatori di avanzamento per il recupero?


99

Faccio fatica a trovare documentazione o esempi di implementazione di un indicatore di avanzamento del caricamento utilizzando fetch .

Questo è l'unico riferimento che ho trovato finora , che afferma:

Gli eventi di avanzamento sono una funzionalità di alto livello che per ora non verrà recuperata. Puoi crearne uno tuo guardando l' Content-Lengthintestazione e utilizzando un flusso pass-through per monitorare i byte ricevuti.

Ciò significa che puoi gestire in modo esplicito le risposte senza un file Content-Lengthdiverso. E ovviamente, anche se Content-Lengthc'è, può essere una bugia. Con i flussi puoi gestire queste bugie come preferisci.

Come scriverei "un flusso pass-through per monitorare i byte" inviati? Se fa qualche tipo di differenza, sto cercando di farlo per potenziare i caricamenti di immagini dal browser a Cloudinary .

NOTA : Sto non interessato alla libreria Cloudinary JS , in quanto dipende da jQuery e la mia app non lo fa. Sono interessato solo all'elaborazione del flusso necessaria per farlo con javascript nativo e fetchpolyfill di Github .


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


Risposte:


44

I flussi stanno iniziando ad arrivare sulla piattaforma web ( https://jakearchibald.com/2016/streams-ftw/ ) ma è ancora agli inizi.

Presto sarai in grado di fornire un flusso come corpo di una richiesta, ma la questione aperta è se il consumo di quel flusso sia correlato ai byte caricati.

Reindirizzamenti particolari possono comportare la ritrasmissione dei dati nella nuova posizione, ma i flussi non possono "riavviarsi". Possiamo risolvere questo problema trasformando il corpo in un callback che può essere chiamato più volte, ma dobbiamo essere sicuri che l'esposizione del numero di reindirizzamenti non sia una perdita di sicurezza, poiché sarebbe la prima volta che sulla piattaforma JS potrebbe rilevarlo.

Alcuni si chiedono se abbia senso collegare il consumo di stream ai byte caricati.

Per farla breve: questo non è ancora possibile, ma in futuro sarà gestito dagli stream o da una sorta di callback di livello superiore passato fetch().


7
Peccato. Per ora lo accetto, ma quando diventerà realtà, spero che qualcun altro pubblicherà una soluzione aggiornata! :)
neezer

1
Aggiornamento: mostra i progressi con l'API di recupero utilizzando gli stream - twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer

2
@EitanPeer Nice. Una cosa simile funzionerà per il caricamento, ad esempio POST?
Michael

4
@EitanPeer Ma la domanda riguarda i progressi nel caricamento, non nel download
John Balvin Arias

1
è il 2020 ora, perché ancora non c'è modo di farlo :(
MHA15

23

La mia soluzione è usare axios , che lo supporta abbastanza bene:

      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,
        //})
      })

Ho un esempio per l'utilizzo di questo in React su GitHub.


2
Questa era anche la mia soluzione. Axios sembra adattarsi molto bene allo stampo.
Jason Rice

1
Fa axiosuso fetcho XMLHttpRequestunder-the-hood?
Dai

3
XMLHttpRequest. Se stai usando questo per React Native, fai attenzione che XMLHttpRequest sembra essere MOLTO MOLTO lento nell'analisi di risposte json di grandi dimensioni rispetto al recupero (circa 10 volte più lento e blocca l'intero thread dell'interfaccia utente).
Cristiano Coelho

21
Non risponde alla domanda! Se la domanda è "come fai x in y?" dire "invece fai x in z" non è una risposta accettabile.
Derek Henderson

3
Questo non risponde alla domanda, soprattutto perché axiosnon usa fetchsotto il cofano e non ha tale supporto. Lo sto letteralmente creando ora per loro, quindi.
sgammon

7

Non credo sia possibile. La bozza afferma:

attualmente manca [ rispetto a XHR ] quando si tratta di richiedere la progressione


(vecchia risposta):
il primo esempio nel capitolo Fetch API fornisce alcune informazioni su come:

Se vuoi ricevere progressivamente i dati del corpo:

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))

A parte il loro uso del Promisecostruttore antipattern , puoi vedere che response.bodyè uno Stream da cui puoi leggere byte per byte usando un Reader, e puoi attivare un evento o fare quello che vuoi (ad esempio registrare l'avanzamento) per ognuno di essi.

Tuttavia, la specifica Streams non sembra essere del tutto finita e non ho idea se funziona già in qualsiasi implementazione di recupero.


11
Se leggo correttamente l'esempio, però, sarebbe per scaricare un file tramite fetch. Mi interessano gli indicatori di avanzamento per il caricamento di un file.
neezer

Oops, quella citazione parla della ricezione di byte, il che mi ha confuso.
Bergi

@Bergi Nota, il Promisecostruttore non è necessario. Response.body.getReader()restituisce a Promise. Vedi come risolvere un errore di intervallo non rilevato durante il download di json di grandi dimensioni
guest271314

3
@ guest271314 sì, l' ho già corretto alla fonte della citazione. E no, getReadernon restituisce una promessa. Non ho idea di cosa abbia a che fare con il post che hai collegato.
Bergi

@Bergi Sì, hai ragione .getReader(), il .read()metodo restituisce a Promise. Questo è ciò che stava cercando di trasmettere. Il collegamento allude alla premessa che se è possibile controllare l'avanzamento per il download, è possibile verificare l'avanzamento per il caricamento. Mettere insieme un modello che restituisca il risultato atteso, in misura apprezzabile; questo è il progresso per il fetch()caricamento. Non ho trovato un modo per echoun oggetto Blobo Filesu jsfiddle, probabilmente manca qualcosa di semplice. Test in fase di localhostcaricamento del file molto rapidamente, senza imitare le condizioni di rete; anche se appena ricordato Network throttling.
ospite 271314

6

Aggiornamento: come dice la risposta accettata, ora è impossibile. ma il codice seguente ha gestito il nostro problema per qualche tempo. Devo aggiungere che almeno abbiamo dovuto passare all'utilizzo di una libreria basata su XMLHttpRequest.

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');
}

grazie a questo link: https://jakearchibald.com/2016/streams-ftw/


2
Bello, ma si applica anche ai caricamenti?
kernel

@kernel ho provato a scoprire ma non sono riuscito a farlo. e mi piace trovare un modo per farlo anche per il caricamento.
Hosseinmp76

Lo stesso, ma finora non sono stato troppo fortunato a trovare / creare un esempio di caricamento funzionante.
kernel

1
content-length! == lunghezza del corpo. Quando viene utilizzata la compressione http (comune per i download di grandi dimensioni), la lunghezza del contenuto è la dimensione dopo la compressione http, mentre la lunghezza è la dimensione dopo che il file è stato estratto.
Ferrybig

@ Ferrybig non ho capito il tuo punto. ho usato l'uguaglianza da qualche parte?
Hosseinmp76

4

Poiché nessuna delle risposte risolve il problema.

Solo per motivi di implementazione, è possibile rilevare la velocità di caricamento con una piccola porzione iniziale di dimensioni note e il tempo di caricamento può essere calcolato con lunghezza del contenuto / velocità di caricamento. Puoi usare questo tempo come stima.


3
Trucco molto intelligente e carino da usare mentre aspettiamo una soluzione in tempo reale :)
Magix


2

Una possibile soluzione sarebbe utilizzare il new Request()costruttore, quindi controllare l' Request.bodyUsed Booleanattributo

Il bodyUsedgetter dell'attributo deve restituire true se disturbede false in caso contrario.

per determinare se il flusso è distributed

Un oggetto che implementa il Bodymixin si dice che sia disturbedse bodynon è nullo e il suo streamè disturbed.

Restituisce il fetch() Promisefrom within .then()chained alla .read()chiamata ricorsiva di a ReadableStreamquando Request.bodyUsedè uguale a true.

Nota, l'approccio non legge i byte di Request.bodyquando i byte vengono trasmessi all'endpoint. Inoltre, il caricamento potrebbe essere completato ben prima che qualsiasi risposta venga restituita per intero al browser.

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));

}

-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}%`;
}

Voglio dare un merito a Benjamin Gruenbaum per l'intera risposta. Perché l'ho imparato dalla sua lezione.
Leon Gilyadov

@LeonGilyadov La conferenza è disponibile online da qualche parte? Un collegamento alla fonte sarebbe carino.
Mark Amery

@MarkAmery Eccolo: youtube.com/watch?v=Ja8GKkxahCo (la conferenza è stata tenuta in ebraico)
Leon Gilyadov

11
La domanda riguarda il caricamento, non il download.
sarneeh

il problema con l'avanzamento del recupero è quando si desidera caricare (non ci sono problemi con il download)
Kamil Kiełczewski

-4

La parte fondamentale è ReadableStreamobj_response .body≫.

Campione:

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() ))

Puoi provare a eseguirlo su una pagina enorme, ad esempio https://html.spec.whatwg.org/ e https://html.spec.whatwg.org/print.pdf . CtrlShiftJ e carica il codice in formato.

(Testato su Chrome.)


Questa risposta ottiene meno punti ma nessuno spiega perché dare un punto negativo - quindi do +1
Kamil Kiełczewski

3
Riceve -1 da me perché non è rilevante per il caricamento .
Brad
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.