Каков наилучший способ показать прогресс по вызову Ajax? - PullRequest
34 голосов
/ 10 октября 2010

У меня есть вызов Ajax, который обновляет 5000 записей в базе данных, так что это занимает много времени. У меня есть Ajax «Загрузка изображения», показывающий, что что-то происходит, но я ищу лучший способ показать «Обновление 50 из 5000 ...», «Обновление 200 из 5000» или что-то в этом роде.

Каков наилучший способ сделать что-то подобное в Ajax / jQuery без 5000 различных сообщений?

Ответы [ 12 ]

23 голосов
/ 20 декабря 2010

Лучшее, что я думаю, это использование Comet .

В приложениях в стиле Comet сервер может по существу передавать данные клиенту (вместо того, чтобы клиент снова запрашивал данные с сервера иснова.).Клиент должен подключаться к серверу только один раз .и затем сервер продолжит отправлять данные обратно клиенту.

Из Википедии:

Comet - это метод программирования, который позволяет веб-серверам отправлять данные клиентубез необходимости клиента запрашивать его.Он позволяет создавать управляемые событиями веб-приложения, которые размещаются в браузере.

А теперь давайте посмотрим, как работает Comet.Смотрите следующий код на стороне сервера.здесь используется цикл while, вместо этого вы можете установить собственное условие.В цикле while страница записывает дату и время и сбрасывается, а затем спит в течение 1/2 секунды.

Кодовая страница ASP.NET: 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();
}

Код на стороне клиента - чистый JavaScript

Сделайте запрос только один раз, а затем продолжайте проверять данные в readyState === 3 из 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(); };

Ссылка

4 голосов
/ 12 октября 2010

Я бы позволил функции, выполняющей большую запись обновления в переменной SESSION, ее текущее выполнение после каждого отдельного (или очень многих) обновлений, и использовал бы отдельный сценарий AJAX, чтобы извлечь это значение прогресса из SESSION и позволить JavaScript использовать это для обновления вашего индикатора выполнения / текста.

2 голосов
/ 15 декабря 2010

Я только что понял идею, читая ответы.

JavaScript и PHP совместно используют файлы cookie,

  1. Создание файла cookie с JavaScript при вызове Ajax.
  2. В файле PHP Ajax увеличивайте значение в этом файле cookie при каждом обновлении SQL.
  3. В JavaScript рекурсивная функция будет считывать этот конкретный файл cookie и обновлять номера индикатора выполнения.

Преимущества:

  1. Только 1 Ajax-вызов.
  2. Меньшая нагрузка на сервер.
2 голосов
/ 14 декабря 2010

Вы можете обновлять буфер ответов с прогрессом, периодически удаляя буфер ответов с сервера.

Но у вас могут возникнуть проблемы с чтением запроса до его завершения через xhttpr. Возможно, вы сможете отправить запрос через iframe, и эта загрузка будет выполняться через потоковую передачу http.

Но даже это может быть отрывочным. HTTP не предназначен для передачи вещей по частям / фрагментирован. Как и другие отмечают, было бы лучше сделать отдельные последующие вызовы, чтобы получить статус операции.

2 голосов
/ 14 декабря 2010

Я бы запускал обратный вызов Ajax один раз каждые n миллисекунд, который может запрашивать объем выполненной работы (например, количество обновленных записей) и использовать его для отображения индикатора выполнения.Что-то вроде того, как это работает.

2 голосов
/ 10 октября 2010

Я предполагаю, что вы в настоящее время используете один POST для всех записей в пакетном обновлении и размещаете загрузочный образ между вызовом и возвратом.

Вместо того, чтобы ждать, пока сервер завершит обновление, немедленно верните его со специальным идентификатором для этого пакетного обновления. Затем выполните серверный вызов, который возвращает состояние пакетного обновления, которое может вызвать ваш диалог прогресса, чтобы сообщить о прогрессе.

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
1 голос
/ 18 декабря 2010

Я не уверен, на какой стороне сервера вы размещаете сообщения, но вы должны быть в состоянии применить этот метод к большинству языков программирования.Я использую PHP в качестве примера.

На стороне HTML есть функция, которая обновляет индикатор выполнения до заданной ширины.Я вызываю эту функцию 'setProgress' для этого примера, которой требуется обновить число для индикатора выполнения до.

В коде на стороне сервера делайте куски обновлений (скажем, 100 за итерацию) и генерируйте вывод,Путем вывода вызова javascript для каждой итерации:

<?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!
?>

После повторения этого, очистите буфер вывода, чтобы убедиться, что эти данные отправляются в браузер.Поскольку это полный тег сценария, браузер выполнит внутри javascript, обновив индикатор выполнения до любого значения, которое вы ему передали.

Чтобы все это работало, вам нужно добавить некоторые вычисления, чтобы иметь смыслиз числа номеров, которые вы звоните на индикатор выполнения, но я предполагаю, что это не должно быть слишком большой проблемой.Вероятно, было бы более разумно использовать setProgress в примере использования процентов, хотя я хотел бы избежать расчетов, необходимых для ясности.

1 голос
/ 10 октября 2010

Я предполагаю, что у вас есть причина для перебора каждой записи по отдельности, вместо того, чтобы просто выполнять оператор SQL.

Если это так, просто делайте вызов ajax каждые 200 итераций или около того.Если вы сделаете это для каждой группы из 200 записей, она будет использовать только 50 вызовов Ajax.

Что-то вроде (псевдокод):

If iterationNumber mod 200 == 0
    // Make Ajax call.
0 голосов
/ 21 декабря 2010

Я когда-то делал нечто подобное (похоже на Зейн Шейх , но проще):

На сервере

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

На клиенте
Следуйте по пути Zain Shaikh , но на ProcessInput просто измените размер вашего индикатора выполнения в соответствии с соотношением между количеством записей для обновления и input.length.

Это решение часто меняет сложность на стороне клиента на пропускную способность сети.

Не смешивайте это действие сервера с возможным импортом файлов, если вы действительно не знаете, что делаете. Вы будете обменивать DB-запросы (выбор для подсчета обновленных строк) на стабильность: что если пользователь изменит страницу? Для индикатора выполнения нет проблем, но импорт не будет завершен.

0 голосов
/ 20 декабря 2010

Создайте простую таблицу, подобную этой:

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

Код бэкэнда (в 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;

Я использовалэтот подход уже используется в 3 приложениях, и я должен сказать, что он очень надежный и быстрый (операция showLog - это просто простая инструкция SELECT).Также можно использовать сессию для хранения прогресса, но это приносит много проблем, так как сессия должна быть закрыта на запись (если она хранится в файлах), в противном случае запросы showLog AJAX будут ждать завершения длинной операции.(и теряет смысл).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...