2010-10-10 14 views
33

Mam wywołanie Ajax, który aktualizuje 5000 rekordów w bazie danych, więc to zajmuje dużo czasu. Mam Ajax "Ładowanie obrazu" pokazujący, że coś się dzieje, ale szukam lepszego sposobu na wyświetlenie "Aktualizowanie 50 na 5000.", "Aktualizacja 200 z 5000", lub coś w tym stylu.Jaki jest najlepszy sposób wyświetlania postępów w wywołaniu Ajax?

Jaki jest najlepszy sposób, żeby zrobić coś takiego w Ajax/jQuery bez robienia 5000 różne posty?

+2

Co to są zabawne instrukcje ładowania, aby użytkownicy byli zadowoleni? http://stackoverflow.com/questions/182112/what-are-some-funny-loading-statements-to-keep-users-amused –

+2

możliwy duplikat [Pokaż postęp podczas długiego połączenia Ajax] (http: // stackoverflow .com/questions/2572830/show-progress-during-a-long-ajax-call) –

+2

@Amr - Ale te nie pokazują postępu, tylko że coś jest ładowane. –

Odpowiedz

23

Najlepsze, jakie myślę, to używanie Comet.

W aplikacjach typu Comet serwer może zasadniczo przekazywać dane do klienta (zamiast danych żądania klienta z serwera wielokrotnie). Klient musi tylko połączyć się z serwerem raz. a następnie serwer będzie kontynuował przesyłanie danych z powrotem do klienta.

Z Wikipedii:

Comet jest techniką, która umożliwia programowanie serwerów internetowych do przesyłania danych do klienta bez jakiejkolwiek potrzeby klienta do żądania go. Umożliwia tworzenie aplikacji internetowych sterowanych zdarzeniami, które są hostowane w przeglądarce.

A teraz zobaczmy, jak działa kometa. Zobacz następujący kod po stronie serwera. tutaj używana jest pętla while, można zamiast tego ustawić własny warunek. W pętli while strona zapisuje datetime i opróżnia się, a następnie śpi przez 1/2 sekundy.

ASP.NET stronę kod z opóźnieniem: Service.aspx.cs

public static string Delimiter = "|"; 

protected void Page_Load(object sender, EventArgs e) 
{ 
    Response.Buffer = false; 

    while (true) 
    { 
     Response.Write(Delimiter 
      + DateTime.Now.ToString("HH:mm:ss.FFF")); 
     Response.Flush(); 

     // Suspend the thread for 1/2 a second 
     System.Threading.Thread.Sleep(500); 
    } 

    // Yes I know we'll never get here, 
    // it's just hard not to include it! 
    Response.End(); 
} 

Client kod side - czysta JavaScript

zrobić tylko żądania raz, a potem zaglądać dane w readyState === 3 z XMLHttpRequest.

function getData() 
{ 
    loadXMLDoc("Service.aspx"); 
} 

var req = false; 
function createRequest() { 
    req = new XMLHttpRequest(); // http://msdn.microsoft.com/en-us/library/ms535874%28v=vs.85%29.aspx 
} 

function loadXMLDoc(url) { 
    try { 
     if (req) { req.abort(); req = false; } 

     createRequest(); 

     if (req) { 
      req.onreadystatechange = processReqChange; 
      req.open("GET", url, true); 
      req.send(""); 
     } 
     else { alert('unable to create request'); } 
    } 
    catch (e) { alert(e.message); } 
} 

function processReqChange() { 
    if (req.readyState == 3) { 
     try { 
      ProcessInput(req.responseText); 

      // At some (artibrary) length recycle the connection 
      if (req.responseText.length > 3000) { lastDelimiterPosition = -1; getData(); } 
     } 
     catch (e) { alert(e.message); } 
    } 
} 

var lastDelimiterPosition = -1; 
function ProcessInput(input) { 
    // Make a copy of the input 
    var text = input; 

    // Search for the last instance of the delimiter 
    var nextDelimiter = text.indexOf('|', lastDelimiterPosition + 1); 
    if (nextDelimiter != -1) { 

     // Pull out the latest message 
     var timeStamp = text.substring(nextDelimiter + 1); 
     if (timeStamp.length > 0) { 
      lastDelimiterPosition = nextDelimiter; 
      document.getElementById('outputZone').innerHTML = timeStamp; 
     } 
    } 
} 

window.onload = function() { getData(); }; 

Reference

+0

uważaj ... są różnice w sposobie, w jaki każda przeglądarka poradzi sobie z tym - aka nie działa we wszystkich. zgadnij, która przeglądarka działa inaczej niż pozostałe. zobacz moją odpowiedź poniżej po więcej szczegółów. – user406905

1

jestem przy założeniu, że masz powód do iteracja każdego rekordu indywidualnie, zamiast po prostu uruchomiony SQL.

Jeśli tak jest, po prostu zrób ajax wywołanie co 200 lub tak iteracji. Jeśli zrobisz to dla każdej grupy z 200 rekordów, zużyje ona tylko 50 wywołań Ajax.

coś jak (Pseudokod):

If iterationNumber mod 200 == 0 
    // Make Ajax call. 
2

jestem zakładając używasz jedno stanowisko dla wszystkich rekordów w aktualizacji wsadowym i umieszczenie obrazu ładowania między rozmową i powrotu.

Zamiast czekać, aż powróci do serwera zakończeniu aktualizacji, nie powróci natychmiast ze specjalnym identyfikatorem tej aktualizacji wsadowym. Następnie zaimplementuj wywołanie serwera, które zwraca status aktualizacji wsadowej, która może zostać wywołana przez okno dialogowe postępu w celu zgłoszenia postępów.

var progressCallback = function(data){ 
    //update progress dialog with data 
    //if data does not indicate completion 
    //ajax call status function with "progressCallback" as callback 
}); 
//ajax call status function with "progressCallback" as callback 
+0

Jest to trochę podobne do tego, jak działa interfejs API REST usługi Splunk (i innych usług internetowych). "Praca" jako przykład - http://www.splunk.com/base/Documentation/latest/Developer/RESTSearch –

+0

http://en.wikipedia.org/wiki/Comet_%28programming%29 (od http://stackoverflow.com/questions/2572830/show-progress-during-a-long-ajax-call) –

4

chciałbym niech funkcję, która robi wielkie rekord aktualizacji w zmiennej sesji obecną postępy po każdym pojedynczym (lub tak wiele) aktualizacji i korzystania z oddzielnego skryptu AJAX pobrać tę wartość postępu z sesji i pozwól JavaScriptowi użyć tego, by zaktualizować pasek postępu/tekst.

+0

to brzmi trochę nieładnie. . czy masz jakieś przykłady tego? – leora

2

Wystrzeliłabym wywołanie zwrotne Ajax po każdym milisekundach, które mogą zapytać, ile zostało zrobione (na przykład liczba zaktualizowanych rekordów) i użyć go do wyświetlenia paska postępu. Działa podobnie do sposobu, w jaki działa this.

2

Można zaktualizować bufor odpowiedzi z postępem, okresowo przepłukując bufor odpowiedzi z serwera.

Ale może masz problem z odczytaniem prośbę zanim będzie kompletna poprzez xhttpr. Możesz przesłać swoją prośbę za pośrednictwem elementu iframe i mieć to obciążenie w toku za pośrednictwem "strumienia http".

Ale nawet to może być szkicowe. HTTP nie jest przeznaczony do przenoszenia elementów po kawałku/fragmentacji. Jak inni podkreślają, najlepiej byłoby wykonać osobne kolejne wywołania, aby uzyskać status operacji.

0

Aby wyświetlić postępy podczas ładowania, zmodyfikuję mój backend tak, aby mógł wykonać selektywne ładowanie.

Na przykład

var total_rec = 5000; 
var load_steps = 20; 
var per_load = total_rev/load_steps; 
var loaded = 0; 
while (loaded < total_rec) { 
    www.foobar.com/api.php?start=loaded&end=(loaded+per_load); 
    loaded += per_load; 
} 

każdym razem, gdy obciążenie jest zrobione, zaktualizować pasek postępu.

Alternatywnym sposobem modyfikowania backend może być

www.foobar.com/api.php?start=loaded&count=50 
2

Właśnie pomysł czytając odpowiedzi.

JavaScript i PHP share ciasteczka,

  1. Tworzenie cookie z JavaScript, gdy wywołanie Ajax jest wykonany.
  2. W pliku Ajax PHP zwiększ wartość tego pliku cookie przy każdej aktualizacji SQL.
  3. W języku JavaScript funkcja rekursywna odczyta ten konkretny plik cookie i zaktualizuje numery pasków postępu.

Zalety:

  1. Tylko 1 Ajax połączenia.
  2. Mniejsze obciążenie serwera.
+1

wow, to możliwe? w jaki sposób będzie zmieniała się wartość cookie między klientem (javascript) a serwerem bez wielu wywołań ajax? –

+0

javascript sprawdzi wartość pliku cookie z funkcją odczytu rekurencyjnego, nie użyje do tego ajaxu. http://www.w3schools.com/js/js_cookies.asp Sprawdź ten link, aby odczytać plik cookie za pomocą javascript –

+0

. Musi istnieć wewnętrzne uzgadnianie między klientem a serwerem w celu odzwierciedlenia aktualizacji plików cookie. Prawdopodobnie jesteśmy w stanie zmienić wartość cookie po stronie serwera i odczytać ją po stronie klienta z powodu tej wewnętrznej komunikacji. Bez tego jak to jest możliwe? –

0

Coś wydaje się podejrzane.

Nie jestem zaznajomiony z bazami danych, które nie są w stanie błyskawicznie zaktualizować 5000 rekordów ... Czyli nie jest to tylko jedna aktualizowana aplikacja, ale rekord po aktualizacji rekordów?

Należy rozważyć system, który pozwala użytkownikowi pobrać 5000 wpisów i nie zaznacza, które z nich zostały edytowane, a następnie pod koniec wprowadzania aktualizacji zastosował pojedynczy przycisk aktualizacji, który wymagałby przekazania w jakikolwiek sposób wszystkich 5000 rekordów. To byłby najgorszy przypadek.

Może być więc jakiś sposób podziału problemu tak, aby nie było czasu oczekiwania. Rozważ na przykład tymczasową tabelę db (lub tylko w pamięci aplikacji, całkiem łatwo, po prostu tworząc listę obiektów ORM ... ale to na marginesie), a kiedy przyjdzie czas, aby zatwierdzić te aktualizacje, mogą one przynajmniej zostać wykonane w wsad bez potrzeby transferu klienta/serwera. Może być nawet możliwe zaznaczenie poszczególnych pól, które zostały zmienione, więc nie ma nic bardziej zaktualizowanego w DB, z wyjątkiem tego, co dokładnie zmieniono.

Są długotrwałe procesy i wiem, że czasami musisz używać tego, co ktoś ci zapewnił ... ale może z odrobiną myślenia możesz po prostu pozbyć się czasu oczekiwania.

1

Nie jestem pewien, na jakiej stronie serwera się publikujesz, ale powinieneś być w stanie zastosować tę metodę w większości języków programowania. Używam PHP jako przykładu.

Po stronie HTML, użyj funkcji, która aktualizuje pasek postępu do określonej szerokości. Nazywam tę funkcję "setProgress" dla tego przykładu, który pobiera numer, aby zaktualizować pasek postępu do.

W kodzie strony po stronie serwera wykonuj fragmenty aktualizacji (powiedzmy 100 na iterację) i generuj dane wyjściowe. Przez wyprowadzanie javascript połączenia dla każdej iteracji:

<?php 
    while() { // Whatever your loop needs to be. 
    // Do your chunk of updates here. 
?> 
    <script type="text/javascript"> 
    setProgress(100); 
    </script> 
<?php 
    flush(); // Send what we have so far to the browser so the script is executed. 
    } 
    setProgress(5000); // All done! 
?> 

Po powtarzając to, opróżnić bufor wyjściowy, aby upewnić się dane te są przesyłane do przeglądarki. Ponieważ jest to kompletny znacznik skryptu, przeglądarka uruchomi javascript wewnątrz, aktualizując pasek postępu do dowolnej wartości przekazanej do niego.

Aby wszystko działało, musisz dodać kilka obliczeń, aby zrozumieć numery, które wywołujesz na pasku postępu, ale zakładam, że nie powinno to być zbyt dużym problemem. Prawdopodobnie bardziej sensownym byłoby mieć setProgress w przykładach wykorzystania procentów, chciałem jednak trzymać się z dala od obliczeń potrzebnych dla jasności.

0

Tworzenie prostej tabeli tak:

kod
CREATE TABLE progress_data (
    statusId int(4) NOT NULL AUTO_INCREMENT, 
    progress float DEFAULT NULL COMMENT 'percentage', 
    PRIMARY KEY (id_progress_data) 
); 

JQuery:

//this uses Jquery Timers http://plugins.jquery.com/project/timers 
$('#bUpdate').click(function() { 
    //first obtain a unique ID of this operation - this has to by synchronized 
    $.ajaxSetup({'async': false}); 
    $.post('ajax.php', {'operation': 'beginOperation'}, function(data) { 
     statusId = parseInt(data.statusId); 
    }); 
    //now run the long-running task with the operation ID and other params as necessary 
    $.ajaxSetup({'async': true}); 
    $.post('ajax.php', {'operation': 'updateSite', 'statusId': statusId, 'param': paramValue}, function(data) { 
     $('#progress_bar').stopTime('statusLog'); //long operation is finished - stop the timer 
     if (data.result) { 
      //operation probably successful 
     } else { 
      //operation failed 
     } 
    }); 
    //query for progress every 4s, 'statusLog' is just the name of the timer 
    $('#progress_bar').everyTime('4s', 'statusLog', function() { 
     var elm = $(this); 
     $.post('ajax.php', {'operation': 'showLog', 'statusId': statusId}, function(data) { 
      if (data) { 
       //set bar percentage 
       $('#progress').css('width', parseInt(data.progress) + '%'); 
      } 
     }); 
    }); 
    return false; 
} 

kod Backend (w PHP):

if (isset($_POST['operation'])) { 
     ini_set("display_errors", false); 
     session_write_close(); //otherwise requests would block each other 
     switch ($_POST['operation']) { 
      /** 
      * Initialize progress operation, acquire ID (statusId) of that operation and pass it back to 
      * JS frontend. The frontend then sends the statusId back to get current state of progress of 
      * a given operation. 
      */ 
      case 'beginOperation': { 
       $statusId = //insert into progress_data 
       echo json_encode(array('statusId' => $statusId)); 
       break; 
      } 
      /** 
      * Return back current progress state. 
      */ 
      case 'showLog': { 
       $result->progress = (float) //SELECT progress FROM progress_data WHERE statusId = $_POST['statusId'] 
       echo json_encode($result); 
       break; 
      } 
      case 'updateSite': { 
       //start long running operation, return whatever you want to, during the operation ocassionally do: 
        UPDATE progress_data SET progress=... WHERE statusId = $_POST['statusId'] 
      } 
     } 
    } 
    /* Terminate script, since this 'view' has no template, there si nothing to display. 
    */ 
    exit; 

Użyłem tego podejścia w 3 aplikacje już i muszę powiedzieć, że jest bardzo niezawodny i szybki enogh (operacja showLog jest po prostu prostą instrukcją SELECT). Możliwe jest również użycie sesji do przechowywania postępu, ale to przynosi wiele problemów, ponieważ sesja musi być zapisana jako zamknięta (jeśli jest przechowywana w plikach), w przeciwnym razie zapytania AJAX showLog będą czekać na zakończenie długiej operacji (i luźny sens).

+0

Twój kod jest za każdym razem odpytywaniem serwera. OP nie chce odpytywania, jak wyraźnie wspomniał * "bez robienia 5000 różnych wpisów" * –

+0

Cóż, ten kod spowodowałby 5000 żądań do serwera, jeśli działa przez 4 godziny. W 10-minutowej operacji będzie to 150 próśb, co jest dość odległe od 5000. Nie powiedział, że chce tego w jednym wniosku. – Odin

0

ja kiedyś coś podobnego w ten sposób (podobny do Zain Shaikh, ale prostszym):

Na serwerze

int toUpdate = 5000; 
int updated = 0; 
int prev = updated; 
while(updated < toUpdate) 
{ 
    updated = getAlreadyUpdatedRows(); 
    flushToClient(generateZeroSequenceOfLength(updated - prev)); 
    prev = updated; 
    sleep(500); 
} 
closeStream(); 

Na kliencie
Postępuj zgodnie ze ścieżką Zain Shaikh, ale na ProcessInput wystarczy zmienić rozmiar paska postępu zgodnie ze stosunkiem między liczbą rekordów do zaktualizowania a wartością input.length.

To rozwiązanie często zajmuje się złożonością strony klienta w zakresie przepustowości sieci.

Nie mieszaj tej aktywności serwera z ewentualnym importem plików, chyba że naprawdę wiesz, co robisz. Będziesz handlować zapytaniami db (wybór dla zliczania zaktualizowanych wierszy) dla stabilności: co jeśli strona zmiany użytkownika? Nie ma problemu z paskiem postępu, ale import nie zostanie ukończony.

Powiązane problemy