Cómo obtener progreso de XMLHttpRequest

133

¿Es posible obtener el progreso de una XMLHttpRequest (bytes cargados, bytes descargados)?

Esto sería útil para mostrar una barra de progreso cuando el usuario está cargando un archivo grande. La API estándar no parece admitirlo, pero ¿tal vez hay alguna extensión no estándar en alguno de los navegadores? Parece una característica bastante obvia después de todo, ya que el cliente sabe cuántos bytes se cargaron / descargaron.

nota: Soy consciente de la alternativa "sondear el progreso del servidor" (es lo que estoy haciendo ahora). El principal problema con esto (que no sea el complicado código del lado del servidor) es que, por lo general, al cargar un archivo grande, la conexión del usuario está completamente retenida, porque la mayoría de los ISP ofrecen un flujo ascendente deficiente. Entonces, hacer solicitudes adicionales no responde tan bien como esperaba. Esperaba que hubiera una forma (tal vez no estándar) de obtener esta información, que el navegador tiene en todo momento.

Pete
fuente

Respuestas:

137

Para los bytes cargados es bastante fácil. Solo monitorea el xhr.upload.onprogressevento. El navegador conoce el tamaño de los archivos que tiene que cargar y el tamaño de los datos cargados, por lo que puede proporcionar la información de progreso.

Para los bytes descargados (al obtener la información xhr.responseText), es un poco más difícil, porque el navegador no sabe cuántos bytes se enviarán en la solicitud del servidor. Lo único que sabe el navegador en este caso es el tamaño de los bytes que está recibiendo.

Hay una solución para esto, es suficiente establecer un Content-Lengthencabezado en el script del servidor, para obtener el tamaño total de los bytes que recibirá el navegador.

Para obtener más información, visite https://developer.mozilla.org/en/Using_XMLHttpRequest .

Ejemplo: el script de mi servidor lee un archivo zip (tarda 5 segundos):

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

Ahora puedo monitorear el proceso de descarga de la secuencia de comandos del servidor, porque sé que es la longitud total:

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(); 
}
albanx
fuente
29
Vale la pena señalar que "Content-Length" no es una longitud estimada, debe ser la longitud exacta, demasiado corta y el navegador cortará la descarga, demasiado tiempo y asumirá que la descarga no se completó.
Chris Chilvers
@ChrisChilvers Eso significa que un archivo PHP puede no calcularse correctamente, ¿verdad?
Hydroper
1
@nicematt en ese ejemplo estaría bien ya que proviene de un archivo, pero si estuviera transmitiendo el zip directamente desde la memoria, no podría simplemente inventar una longitud de contenido o estimarlo.
Chris Chilvers
3
@TheProHands Cuando visita una página .php, el servidor ejecuta el archivo PHP y le envía su salida . El servidor debería enviar la longitud de la salida, no el archivo .php.
leewz
¿Alguien puede explicar cuál es la línea "$ ('# barra de progreso'). Barra de progreso (" opción "," valor ", porcentajeCompletar);" ¿medio? ¿Esto invoca un complemento específico? ¿Como funciona esto? Haga que la respuesta sea comprensible para los nuevos usuarios.
Zac
8

Uno de los enfoques más prometedores parece ser abrir un segundo canal de comunicación de regreso al servidor para preguntarle cuánto se ha completado la transferencia.

Sean McMains
fuente
24
Creo que el enlace podría estar muerto
Ronnie Royston
5

Para el total cargado, no parece haber una forma de manejar eso, pero hay algo similar a lo que desea descargar. Una vez que readyState es 3, puede consultar periódicamente responseText para obtener todo el contenido descargado hasta una Cadena (esto no funciona en IE), hasta que todo esté disponible, momento en el que pasará a readyState 4. El total Los bytes descargados en un momento dado serán iguales al total de bytes en la cadena almacenada en responseText.

Para un enfoque de todo o nada a la pregunta de carga, ya que debe pasar una cadena para cargar (y es posible determinar los bytes totales de eso), el total de bytes enviados para readyState 0 y 1 será 0, y el total para readyState 2 serán el total de bytes en la cadena que ingresó. El total de bytes enviados y recibidos en readyState 3 y 4 será la suma de los bytes en la cadena original más el total de bytes en responseText.

Orclev
fuente
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>

Resultado

Foros Amante
fuente
2

Si tiene acceso a su instalación de apache y confía en el código de terceros, puede usar el módulo de progreso de carga de apache (si usa apache; también hay un módulo de progreso de carga nginx ).

De lo contrario, tendría que escribir una secuencia de comandos que pueda golpear fuera de banda para solicitar el estado del archivo (por ejemplo, verificando el tamaño del archivo tmp).

Creo que se está trabajando en Firefox 3 para agregar soporte de progreso de carga al navegador, pero eso no entrará en todos los navegadores y será ampliamente adoptado por un tiempo (es una pena).

Eón
fuente
-7

La única forma de hacerlo con JavaScript puro es implementar algún tipo de mecanismo de sondeo. Deberá enviar solicitudes ajax a intervalos fijos (cada 5 segundos, por ejemplo) para obtener la cantidad de bytes recibidos por el servidor.

Una forma más eficiente sería usar flash. El componente flexible FileReference distribuye periódicamente un evento de 'progreso' que contiene el número de bytes ya cargados. Si necesita seguir con JavaScript, hay puentes disponibles entre actionscript y javascript. La buena noticia es que este trabajo ya está hecho para ti :)

swfupload

Esta biblioteca permite registrar un controlador javascript en el evento de progreso flash.

Esta solución tiene la gran ventaja de no requerir recursos adicionales en el lado del servidor.

Alexandre Victoor
fuente