Come ottenere progressi da XMLHttpRequest


133

È possibile ottenere lo stato di avanzamento di un XMLHttpRequest (byte caricati, byte scaricati)?

Ciò sarebbe utile per mostrare una barra di avanzamento quando l'utente sta caricando un file di grandi dimensioni. L'API standard non sembra supportarla, ma forse c'è qualche estensione non standard in nessuno dei browser là fuori? Dopotutto sembra una funzionalità abbastanza ovvia, dal momento che il client sa quanti byte sono stati caricati / scaricati.

nota: sono a conoscenza dell'alternativa "polling server for progress" (è quello che sto facendo in questo momento). il problema principale con questo (a parte il complicato codice lato server) è che in genere, durante il caricamento di un file di grandi dimensioni, la connessione dell'utente viene completamente nascosta, poiché la maggior parte degli ISP offre scarsi upstream. Quindi fare richieste extra non è così reattivo come speravo. Speravo che ci fosse un modo (forse non standard) per ottenere queste informazioni, che il browser ha sempre.

Risposte:


137

Per i byte caricati è abbastanza semplice. Basta monitorare l' xhr.upload.onprogressevento. Il browser conosce la dimensione dei file che deve caricare e la dimensione dei dati caricati, quindi può fornire le informazioni sullo stato di avanzamento.

Per i byte scaricati (quando si ottengono le informazioni con xhr.responseText), è un po 'più difficile, perché il browser non sa quanti byte verranno inviati nella richiesta del server. L'unica cosa che il browser conosce in questo caso è la dimensione dei byte che sta ricevendo.

C'è una soluzione per questo, è sufficiente impostare Content-Lengthun'intestazione sullo script del server, al fine di ottenere la dimensione totale dei byte che il browser riceverà.

Per ulteriori informazioni, visitare https://developer.mozilla.org/en/Using_XMLHttpRequest .

Esempio: lo script del mio server legge un file zip (richiede 5 secondi):

$filesize=filesize('test.zip');

header("Content-Length: " . $filesize); // set header length
// if the headers is not set then the evt.loaded will be 0
readfile('test.zip');
exit 0;

Ora posso monitorare il processo di download dello script del server, perché so che è la lunghezza totale:

function updateProgress(evt) 
{
   if (evt.lengthComputable) 
   {  // evt.loaded the bytes the browser received
      // evt.total the total bytes set by the header
      // jQuery UI progress bar to show the progress on screen
     var percentComplete = (evt.loaded / evt.total) * 100;  
     $('#progressbar').progressbar( "option", "value", percentComplete );
   } 
}   
function sendreq(evt) 
{  
    var req = new XMLHttpRequest(); 
    $('#progressbar').progressbar();    
    req.onprogress = updateProgress;
    req.open('GET', 'test.php', true);  
    req.onreadystatechange = function (aEvt) {  
        if (req.readyState == 4) 
        {  
             //run any callback here
        }  
    };  
    req.send(); 
}

29
Vale la pena notare che "Content-Length" non è una lunghezza stimata, deve essere la lunghezza esatta, troppo corta e il browser interromperà il download, troppo a lungo e supporrà che il download non sia stato completato.
Chris Chilvers,

@ChrisChilvers Ciò significa che un file PHP potrebbe non essere calcolato correttamente, giusto?
Idroper

1
@nicematt in quell'esempio andrebbe bene dato che proviene da un file, ma se stavi trasmettendo lo zip direttamente dalla memoria non potresti semplicemente inventare una lunghezza del contenuto o stimarlo.
Chris Chilvers,

3
@TheProHands Quando visiti una pagina .php, il server esegue il file PHP e ti invia il suo output . Il server dovrebbe inviare la lunghezza dell'output, non il file .php.
leewz,

Qualcuno può spiegare cosa è la riga "$ ('# progressbar'). Progressbar (" opzione "," valore ", percentComplete);" si intende? Si sta invocando un plug-in specifico? Come funziona? Rendi comprensibile la risposta ai nuovi utenti.
Zac,


8

Uno degli approcci più promettenti sembra riaprire un secondo canale di comunicazione al server per chiedergli quanto del trasferimento è stato completato.


24
Penso che il collegamento potrebbe essere morto
Ronnie Royston il


5

Per il totale caricato non sembra esserci un modo per gestirlo, ma c'è qualcosa di simile a quello che vuoi scaricare. Una volta che readyState è 3, è possibile eseguire periodicamente una query di responseText per ottenere tutto il contenuto scaricato fino a una stringa (non funziona in IE), fino a quando non è disponibile tutto, a quel punto passerà a readyState 4. Il totale i byte scaricati in qualsiasi momento saranno uguali ai byte totali nella stringa memorizzata in responseText.

Per un approccio tutto o niente alla domanda di caricamento, poiché è necessario passare una stringa per il caricamento (ed è possibile determinarne i byte totali), il totale dei byte inviati per readyState 0 e 1 sarà 0 e il totale per readyState 2 saranno i byte totali nella stringa che hai passato. I byte totali inviati e ricevuti in readyState 3 e 4 saranno la somma dei byte nella stringa originale più i byte totali in responseText.


3

<!DOCTYPE html>
<html>
<body>
<p id="demo">result</p>
<button type="button" onclick="get_post_ajax();">Change Content</button>
<script type="text/javascript">
	function update_progress(e)
	{
	  if (e.lengthComputable)
	  {
	    var percentage = Math.round((e.loaded/e.total)*100);
	    console.log("percent " + percentage + '%' );
	  }
	  else 
	  {
	  	console.log("Unable to compute progress information since the total size is unknown");
	  }
	}
	function transfer_complete(e){console.log("The transfer is complete.");}
	function transfer_failed(e){console.log("An error occurred while transferring the file.");}
	function transfer_canceled(e){console.log("The transfer has been canceled by the user.");}
	function get_post_ajax()
	{
	  	var xhttp;
	  	if (window.XMLHttpRequest){xhttp = new XMLHttpRequest();}//code for modern browsers} 
	 	else{xhttp = new ActiveXObject("Microsoft.XMLHTTP");}// code for IE6, IE5	  	
	  	xhttp.onprogress = update_progress;
		xhttp.addEventListener("load", transfer_complete, false);
		xhttp.addEventListener("error", transfer_failed, false);
		xhttp.addEventListener("abort", transfer_canceled, false);	  	
	  	xhttp.onreadystatechange = function()
	  	{
	    	if (xhttp.readyState == 4 && xhttp.status == 200)
	    	{
	      		document.getElementById("demo").innerHTML = xhttp.responseText;
	    	}
	  	};
	  xhttp.open("GET", "http://it-tu.com/ajax_test.php", true);
	  xhttp.send();
	}
</script>
</body>
</html>

Risultato


2

Se hai accesso alla tua installazione di apache e ti fidi del codice di terze parti, puoi utilizzare il modulo di avanzamento del caricamento di apache (se usi apache; esiste anche un modulo di avanzamento del caricamento di nginx ).

Altrimenti, dovresti scrivere uno script che puoi colpire fuori banda per richiedere lo stato del file (controllando ad esempio la dimensione del file tmp).

Ci sono alcuni lavori in corso su Firefox 3, credo che aggiungerò il supporto per l'avanzamento del caricamento nel browser, ma questo non entrerà in tutti i browser e sarà ampiamente adottato per un po '(più è un peccato).


-7

L'unico modo per farlo con javascript puro è implementare un qualche tipo di meccanismo di polling. Sarà necessario inviare richieste Ajax a intervalli fissi (ogni 5 secondi ad esempio) per ottenere il numero di byte ricevuti dal server.

Un modo più efficiente sarebbe usare il flash. Il componente Flex FileReference invia periodicamente un evento "progress" contenente il numero di byte già caricati. Se è necessario attenersi a JavaScript, sono disponibili ponti tra ActionScript e JavaScript. La buona notizia è che questo lavoro è già stato fatto per te :)

swfupload

Questa libreria consente di registrare un gestore javascript sull'evento di avanzamento flash.

Questa soluzione ha il vantaggio di non richiedere risorse aditionali sul lato server.

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.