2016-02-29 3 views
37

Non riesco a trovare documentazione o esempi di implementazione di un indicatore di avanzamento caricamento utilizzando fetch.Indicatori di avanzamento del caricamento per il recupero?

This is the only reference I've found so far, in cui si afferma:

eventi progress sono una caratteristica di alto livello che non si arriverà a prendere per il momento. Puoi crearne uno personalizzato guardando l'intestazione Content-Length e utilizzando un flusso pass-through per monitorare i byte ricevuti.

Ciò significa che è possibile gestire in modo esplicito le risposte senza un Content-Length in modo diverso. E naturalmente, anche se Content-Length è lì può essere una bugia. Con i flussi puoi gestire queste bugie come preferisci.

Come scrivere "un flusso pass-through per monitorare i byte" inviato? Se fa qualsiasi tipo di differenza, sto cercando di fare questo per alimentare i caricamenti di immagini dal browser a Cloudinary.

NOTA: Sono non interessato al Cloudinary JS library, in quanto dipende jQuery e la mia app non lo fa. Mi interessa solo l'elaborazione del flusso necessaria per farlo con javascript nativo e il polyfill fetch di Github.


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

+3

@Magix Vedi [Interruzione di un recupero: The Next Generation # 447]. (Https: // GitHub.it/whatwg/fetch/issues/447) – guest271314

risposta

23

Gli stream stanno iniziando ad atterrare nella piattaforma web (https://jakearchibald.com/2016/streams-ftw/) ma sono ancora in anticipo.

Presto sarà possibile fornire uno stream come corpo di una richiesta, ma la domanda aperta è se il consumo di quel flusso si riferisce ai byte caricati.

Particolari reindirizzamenti possono comportare il ritrasmissione di 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 è una perdita di sicurezza, dal momento che sarebbe la prima volta sulla piattaforma che JS potrebbe rilevalo.

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

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

+4

Peccato. Accettando questo per ora, ma quando questo diventa realtà, spero che qualcun altro pubblicherà una soluzione aggiornata! :) – neezer

+0

@ jaffa-the-cake ci sono novità? Aggiornamento – mu3

+1

- mostra i progressi con API di recupero utilizzando gli stream - https://twitter.com/umaar/status/917789464658890753/photo/1 –

5

non credo che sia possibile. La bozza afferma:

è attualmente manca [in confronto a XHR] quando si tratta di richiedere la progressione


(risposta vecchia):
Il primo esempio in il Fetch API chapter fornisce alcune informazioni su come:

Se si desidera ricevere i dati del corpo progressivamente:

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

Oltre al loro uso del Promise constructor antipattern, si può vedere che response.body è un flusso da cui si può leggere byte per byte utilizzando un lettore, e si può sparare un evento o fare qualsiasi cosa tu voglia (es registra il progresso) per ognuno di essi.

Tuttavia, lo Streams spec non sembra essere terminato, e non ho idea se questo già funzioni in qualsiasi implementazione di recupero.

+5

Se leggo correttamente questo esempio, però, questo sarebbe per ** scaricare ** un file tramite 'fetch'. Sono interessato agli indicatori di avanzamento per ** caricare ** un file. – neezer

+0

Oops, quella citazione parla di * ricezione * di byte, che mi ha confuso. – Bergi

+0

@Bergi Nota, il costruttore 'Promise' non è necessario. 'Response.body.getReader()' restituisce un 'Promise'. Vedi [Come risolvere Uncaught RangeError quando scarichi json di grandi dimensioni] (http://stackoverflow.com/questions/39959467/how-to-solve-uncaught-rangeerror-when-download-large-size-json) – guest271314

2

Una possibile soluzione potrebbe essere quella di utilizzare new Request() costruttore quindi controllare Request.bodyUsedBoolean attributo

getter dell'attributo bodyUsed deve restituire true se disturbed, e false altrimenti.

per determinare se il flusso è distributed

Un oggetto che implementa l'Body mixin è detto disturbed se body è non nullo e la sua stream è disturbed.

ritorno il fetch()Promise dall'interno .then() incatenato al ricorsive .read() richiamo di un ReadableStream quando Request.bodyUsed è uguale a true.

Nota: l'approccio non legge i byte dello Request.body mentre i byte sono trasmessi in streaming all'endpoint. Inoltre, il caricamento potrebbe essere completato molto 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

Dal momento che nessuna delle risposte risolvere il problema.

Solo per motivi di implementazione, è possibile rilevare la velocità di caricamento with some small initial chunk of known size e il tempo di caricamento può essere calcolato con la lunghezza del contenuto/velocità di caricamento. Puoi usare questo tempo come stima.

+1

Un trucco molto intelligente e piacevole da usare mentre aspettiamo una soluzione in tempo reale :) – Magix

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

non funzionerà mai perché 'ReadableStreamDefaultReader' non ha una proprietà' byteLength'. – silicakes

+0

Voglio dare un credito a Benjamin Gruenbaum per l'intera risposta. Perché l'ho imparato dalla sua lezione. –