Jak uzyskać postęp z XMLHttpRequest

133

Czy można uzyskać postęp XMLHttpRequest (przesłane bajty, pobrane bajty)?

Byłoby to przydatne, aby wyświetlić pasek postępu, gdy użytkownik przesyła duży plik. Wydaje się, że standardowe API go nie obsługuje, ale może istnieje jakieś niestandardowe rozszerzenie w którejkolwiek z przeglądarek? Wydaje się, że to dość oczywista funkcja, ponieważ klient wie, ile bajtów zostało przesłanych / pobranych.

uwaga: zdaję sobie sprawę z alternatywy „sonduj serwer w poszukiwaniu postępu” (właśnie to robię w tej chwili). główny problem z tym (inny niż skomplikowany kod po stronie serwera) polega na tym, że zazwyczaj podczas przesyłania dużego pliku połączenie użytkownika jest całkowicie zamknięte, ponieważ większość dostawców usług internetowych oferuje słabe przesyłanie danych. Dlatego tworzenie dodatkowych próśb nie jest tak responsywne, jak się spodziewałem. Miałem nadzieję, że będzie sposób (może niestandardowy), aby uzyskać te informacje, które przeglądarka ma przez cały czas.

Pete
źródło

Odpowiedzi:

137

Dla przesłanych bajtów jest to dość łatwe. Po prostu obserwuj xhr.upload.onprogresswydarzenie. Przeglądarka zna rozmiar plików, które musi przesłać, i rozmiar przesłanych danych, dzięki czemu może dostarczyć informacje o postępie.

W przypadku pobranych bajtów (podczas pobierania informacji xhr.responseText) jest to trochę trudniejsze, ponieważ przeglądarka nie wie, ile bajtów zostanie wysłanych w żądaniu serwera. Jedyne, co przeglądarka wie w tym przypadku, to rozmiar odbieranych bajtów.

Jest na to rozwiązanie, wystarczy ustawić Content-Lengthnagłówek w skrypcie serwera, aby uzyskać całkowity rozmiar bajtów, które przeglądarka ma otrzymać.

Więcej informacji można znaleźć pod adresem https://developer.mozilla.org/en/Using_XMLHttpRequest .

Przykład: mój skrypt serwera odczytuje plik zip (zajmuje to 5 sekund):

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

Teraz mogę monitorować proces pobierania skryptu serwera, ponieważ znam jego całkowitą długość:

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
źródło
29
Warto zauważyć, że „Content-Length” nie jest szacowaną długością, musi to być dokładna długość, zbyt krótka, a przeglądarka przerwie pobieranie, zbyt długo, a przeglądarka założy, że pobieranie się nie udało.
Chris Chilvers,
@ChrisChilvers To oznacza, że ​​plik PHP może nie zostać poprawnie obliczony, prawda?
Hydroper
1
@nicematt w tym przykładzie byłoby w porządku, ponieważ pochodzi z pliku, ale jeśli przesyłasz strumieniowo zip bezpośrednio z pamięci, nie możesz po prostu określić długości zawartości lub jej oszacować.
Chris Chilvers
3
@TheProHands Kiedy odwiedzasz stronę .php, serwer uruchamia plik PHP i wysyła ci wynik . Serwer powinien wysyłać długość wyjścia, a nie plik .php.
leewz
Czy ktoś może wyjaśnić wiersz "$ ('# progressbar'). Progressbar (" opcja "," wartość ", procentComplete);" znaczy? Czy to wywołuje określoną wtyczkę? Jak to działa? Prosimy, aby odpowiedź była zrozumiała dla nowych użytkowników.
Zac
8

Wydaje się, że jednym z najbardziej obiecujących podejść jest otwarcie drugiego kanału komunikacyjnego z powrotem do serwera, aby zapytać go, jaka część transferu została zakończona.

Sean McMains
źródło
24
Myślę, że link może być martwy
Ronnie Royston
5

Wydaje się, że nie ma sposobu, aby sobie z tym poradzić, ale jest coś podobnego do tego, co chcesz pobrać. Gdy readyState ma wartość 3, możesz okresowo wysyłać zapytania responseText, aby pobrać całą pobraną zawartość jako ciąg znaków (to nie działa w IE), aż do momentu, gdy wszystko będzie dostępne, w którym to momencie przejdzie do readyState 4. Łącznie bajty pobrane w dowolnym momencie będą równe sumie bajtów w ciągu przechowywanym w responseText.

W przypadku podejścia typu wszystko albo nic do pytania wysyłania, ponieważ musisz przekazać ciąg do przesłania (i można określić całkowitą liczbę bajtów), całkowita liczba bajtów wysłanych dla readyState 0 i 1 będzie równa 0, a suma dla readyState 2 będzie całkowitą liczbą bajtów w przekazanym ciągu. Suma bajtów zarówno wysłanych, jak i odebranych w readyState 3 i 4 będzie sumą bajtów w oryginalnym ciągu plus łączna liczba bajtów w responseText.

Orclev
źródło
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>

Wynik

Miłośnik forum
źródło
2

Jeśli masz dostęp do swojej instalacji Apache i ufasz kodowi innej firmy, możesz użyć modułu postępu przesyłania apache (jeśli używasz apache; istnieje również moduł postępu przesyłania Nginx ).

W przeciwnym razie musiałbyś napisać skrypt, który możesz trafić poza pasmo, aby zażądać statusu pliku (na przykład sprawdzając rozmiar pliku tmp).

Wydaje mi się, że w przeglądarce Firefox 3 trwają prace nad dodaniem obsługi postępu przesyłania do przeglądarki, ale to nie dotrze do wszystkich przeglądarek i przez jakiś czas zostanie powszechnie przyjęte (a szkoda).

Eon
źródło
-7

Jedynym sposobem na to w czystym javascript jest zaimplementowanie pewnego rodzaju mechanizmu odpytywania. Będziesz musiał wysyłać żądania Ajax w stałych odstępach czasu (na przykład co 5 sekund), aby uzyskać liczbę bajtów odebranych przez serwer.

Bardziej wydajnym sposobem byłoby użycie lampy błyskowej. Składnik Flex FileReference okresowo wysyła zdarzenie „progress” zawierające liczbę przesłanych już bajtów. Jeśli chcesz trzymać się javascript, dostępne są pomosty między skryptami akcji i javascript. Dobra wiadomość jest taka, że ​​ta praca została już wykonana dla Ciebie :)

swfupload

Ta biblioteka umożliwia zarejestrowanie modułu obsługi javascript na zdarzenie postępu flash.

To rozwiązanie ma ogromną zaletę polegającą na tym, że nie wymaga dodatkowych zasobów po stronie serwera.

Alexandre Victoor
źródło