Como obter progresso do XMLHttpRequest

133

É possível obter o progresso de um XMLHttpRequest (bytes carregados, bytes baixados)?

Isso seria útil para mostrar uma barra de progresso quando o usuário estiver carregando um arquivo grande. A API padrão não parece suportá-la, mas talvez haja alguma extensão fora do padrão em qualquer navegador? Parece um recurso bastante óbvio, afinal, já que o cliente sabe quantos bytes foram carregados / baixados.

nota: estou ciente da alternativa "pesquisar no servidor para obter progresso" (é o que estou fazendo agora). o principal problema com isso (que não seja o código complicado do lado do servidor) é que, normalmente, ao fazer o upload de um arquivo grande, a conexão do usuário é completamente integrada, porque a maioria dos ISPs oferece um upstream ruim. Portanto, fazer solicitações extras não é tão responsivo quanto eu esperava. Eu esperava que houvesse uma maneira (talvez não-padrão) de obter essas informações, que o navegador sempre possui.

Pete
fonte

Respostas:

137

Para os bytes enviados, é bastante fácil. Apenas monitore o xhr.upload.onprogressevento. O navegador sabe o tamanho dos arquivos a serem carregados e o tamanho dos dados carregados, para que possa fornecer as informações de progresso.

Para os bytes baixados (ao obter as informações xhr.responseText), é um pouco mais difícil, porque o navegador não sabe quantos bytes serão enviados na solicitação do servidor. A única coisa que o navegador sabe nesse caso é o tamanho dos bytes que está recebendo.

Existe uma solução para isso, basta definir um Content-Lengthcabeçalho no script do servidor, para obter o tamanho total dos bytes que o navegador receberá.

Para mais informações, visite https://developer.mozilla.org/en/Using_XMLHttpRequest .

Exemplo: o script do meu servidor lê um arquivo zip (leva 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;

Agora eu posso monitorar o processo de download do script do servidor, porque sei que é o tamanho 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
fonte
29
Vale ressaltar que "Comprimento do conteúdo" não é um tamanho estimado, deve ser o tamanho exato, muito curto e o navegador interromperá o download por muito tempo e o navegador assumirá que o download não foi concluído.
21812 Chris Chilvers #
@ChrisChilvers Isso significa que um arquivo PHP pode não ser calculado corretamente, certo?
24516
1
@nicematt nesse exemplo seria bom, pois vem de um arquivo, mas se você estivesse transmitindo o arquivo zip diretamente da memória, não conseguiria apenas compor um tamanho de conteúdo ou estimar.
precisa saber é o seguinte
3
@TheProHands Quando você visita uma página .php, o servidor executa o arquivo PHP e envia sua saída . O servidor deve enviar o comprimento da saída, não o arquivo .php.
leewz
Alguém pode explicar o que a linha "$ ('# progressbar'). Progressbar (" option "," value ", percentComplete);" significa? Isso está chamando um plug-in específico? Como é que isso funciona? Faça a resposta compreensível para novos usuários.
Zac
8

Uma das abordagens mais promissoras parece estar abrindo um segundo canal de comunicação de volta ao servidor para perguntar quanto da transferência foi concluída.

Sean McMains
fonte
24
Eu acho que o link poderia estar morto
Ronnie Royston
5

Para o total carregado, não parece haver uma maneira de lidar com isso, mas há algo semelhante ao que você deseja baixar. Depois que o readyState for 3, você poderá consultar periodicamente o responseText para obter todo o conteúdo baixado até uma String (isso não funciona no IE), até que todo esteja disponível; nesse momento, ele fará a transição para o readyState 4. O total os bytes baixados a qualquer momento serão iguais ao total de bytes na cadeia armazenada em responseText.

Para uma abordagem tudo ou nada da pergunta de upload, já que você precisa passar uma string para upload (e é possível determinar o total de bytes disso), o total de bytes enviados para readyState 0 e 1 será 0 e o total para readyState 2 será o total de bytes na string que você digitou. O total de bytes enviados e recebidos no readyState 3 e 4 será a soma dos bytes na string original mais o total de bytes no responseText.

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

Amante de fóruns
fonte
2

Se você tiver acesso à instalação do apache e confiar no código de terceiros, poderá usar o módulo de progresso do upload do apache (se você usar o apache; também há um módulo de progresso do upload do nginx ).

Caso contrário, você teria que escrever um script que possa atingir fora da banda para solicitar o status do arquivo (verificando o tamanho do arquivo tmp, por exemplo).

Acredito que há algum trabalho em andamento no firefox 3 para adicionar suporte ao progresso de upload no navegador, mas isso não entrará em todos os navegadores e será amplamente adotado por um tempo (mais é uma pena).

Aeon
fonte
-7

A única maneira de fazer isso com javascript puro é implementar algum tipo de mecanismo de pesquisa. Você precisará enviar solicitações ajax em intervalos fixos (a cada 5 segundos, por exemplo) para obter o número de bytes recebidos pelo servidor.

Uma maneira mais eficiente seria usar o flash. O componente flex FileReference envia periodicamente um evento 'progress' contendo o número de bytes já carregados. Se você precisar usar o javascript, há pontes disponíveis entre o actionscript e o javascript. A boa notícia é que este trabalho já foi feito para você :)

swfupload

Essa biblioteca permite registrar um manipulador de javascript no evento de progresso do flash.

Esta solução tem a vantagem de não exigir recursos adicionais no lado do servidor.

Alexandre Victoor
fonte