显示Ajax调用进展的最佳方式是什么?

时间:2022-02-22 23:14:09

I have an Ajax call that updates 5,000 records in a database so this takes a lot of time. I have an Ajax "Loading image" showing that something is happening, but I am looking for a better way to show "Updating 50 of 5000 . . .", "Updating 200 of 5000", or something like that.

我有一个Ajax调用,它更新数据库中的5000条记录,所以这需要很多时间。我有一个Ajax的“加载图像”显示正在发生一些事情,但是我正在寻找一种更好的方式来显示“更新50 / 5000”、“更新200 / 5000”或类似的东西。

What is the best way to do something like this in Ajax / jQuery without doing 5000 different posts?

使用Ajax / jQuery而不发布5000个不同的帖子的最好方法是什么?

12 个解决方案

#1


23  

The best I think is using Comet.

我认为最好的方法是使用Comet。

In Comet-style applications, the server can essentially push data to the client (instead of the client request data from the server again and again.). The client needs to only connect to server once. and then server will keep pushing data back to client.

在comet风格的应用程序中,服务器基本上可以将数据推送到客户机(而不是反复地从服务器请求客户机数据)。客户端只需要连接一次服务器。然后服务器会继续将数据推送到客户端。

From Wikipedia:

从*:

Comet is a programming technique that enables web servers to send data to the client without having any need for the client to request it. It allows creation of event-driven web applications which are hosted in the browser.

Comet是一种编程技术,它允许web服务器向客户机发送数据,而不需要客户机请求数据。它允许在浏览器中创建事件驱动的web应用程序。

And now let's see how Comet works. See the following server-side code. here a while loop is being used, you can instead set your own condition. In the while loop, the page writes a datetime and flushes and then sleeps for 1/2 seconds.

现在让我们看看Comet是如何工作的。请参阅下面的服务器端代码。这里使用了一个while循环,您可以设置自己的条件。在while循环中,页面写入一个datetime,刷新后休眠1/2秒。

ASP.NET page code-behind: Service.aspx.cs

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

Client side code - pure JavaScript

客户端代码——纯JavaScript

Only make the request once, and then keep checking the data in the readyState === 3 of XMLHttpRequest.

只请求一次,然后在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(); };

Reference

参考

#2


4  

I would let the function that is doing the big update record in a SESSION variable its current progress after each single (or so many) update, and use a separate AJAX script to retrieve this progress value from the SESSION and let JavaScript use this to update your progress bar/text.

我将让在会话变量中执行大更新记录的函数在每次(或多次)更新后执行当前进度,并使用单独的AJAX脚本从会话中检索此进度值,并让JavaScript使用它更新进度条/文本。

#3


2  

I am assuming you are currently using one POST for all records in the batch update and placing the loading image between call and return.

我假设您目前正在对批处理更新中的所有记录使用一个POST,并在调用和返回之间放置加载映像。

Rather than having the server wait to return until completing the update, have it return immediately with a special ID for that batch update. Then implement a server call that returns the status of the batch update, which your progress dialog can call to report the progress.

与其让服务器等待返回,直到完成更新,不如让它立即返回,并为该批更新提供一个特殊的ID。然后实现一个服务器调用,返回批处理更新的状态,您的进度对话框可以调用它来报告进度。

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

#4


2  

I would fire an Ajax callback once every n milliseconds that can query on how much is done (e.g. number of records updated) and use that to display a progress bar. Something like the way this works.

我将在每n毫秒的时间内触发一个Ajax回调,它可以查询完成了多少工作(例如更新的记录数量),并使用它来显示进度条。就像这样。

#5


2  

I just got an idea while reading the replies.

我在看回信的时候想到了一个主意。

JavaScript and PHP share cookies,

JavaScript和PHP共享cookie,

  1. Create a cookie with JavaScript when an Ajax call is made.
  2. 在生成Ajax调用时,用JavaScript创建一个cookie。
  3. In an Ajax PHP file increment the value in that cookie with each SQL update.
  4. 在Ajax PHP文件中,每次SQL更新时都要增加cookie中的值。
  5. In JavaScript a recursive function will read that particular cookie and will update progress bar numbers.
  6. 在JavaScript中,递归函数将读取特定的cookie并更新进度条号。

Advantages:

优点:

  1. Only 1 Ajax call.
  2. 只有一个Ajax调用。
  3. Less load on the server.
  4. 减少服务器上的负载。

#6


2  

You could update the response buffer with a progress, flushing your response buffer periodically from the server.

您可以使用进度更新响应缓冲区,定期从服务器刷新响应缓冲区。

But you may have trouble reading a request before it's complete via xhttpr. You might be able to make your request via an iframe, and have that load in progress via 'http streaming'.

但是在通过xhttpr完成请求之前,您可能会遇到读取请求的困难。您可能可以通过iframe发出请求,并通过“http流”进行加载。

But even that can be sketchy. HTTP is not the meant to transfer things piecemeal/fragmented. Like others point out, it would be best to make a separate subsequent calls to get the status of the operation.

但即使是这样也可能是粗略的。HTTP并不是用来传输零碎/片段的。就像其他人指出的那样,最好进行单独的后续调用,以获得操作的状态。

#7


1  

I am assuming that you have a reason for iterating through each record individually, instead of simply running a SQL statement.

我假设您有一个可以逐个遍历每个记录的理由,而不是简单地运行一个SQL语句。

If that is the case, simply make an ajax call every 200 or so iterations. If you do it for each group of 200 records, it will only consume 50 Ajax calls.

如果是这样,只需每大约200次迭代一次ajax调用。如果对每组200条记录都这样做,那么只会消耗50次Ajax调用。

Something like (pseudocode):

类似(伪代码):

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

#8


1  

I'm not sure what the server-side is where you are posting to, but you should be able to apply this method to most programming languages. I'm using PHP as an example.

我不确定要发布到的服务器端是什么,但是您应该能够将此方法应用到大多数编程语言中。我用PHP作为例子。

On the HTML-side, have some function that updates the progress bar to a given width. I'm calling this function 'setProgress' for this example, which takes a number to update the progress bar to.

在html端,有一些函数将进度条更新为给定的宽度。我为这个示例调用这个函数“setProgress”,它需要一个数字来更新进度条。

In the server-side code, do chunks of updates (say 100 per iteration) and generate output. By outputting a javascript call for each iteration:

在服务器端代码中,执行大量更新(比如每次迭代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!
?>

After echoing this, flush the output buffer to make sure this data is sent to the browser. Because it is a complete script tag, the browser will execute the javascript inside, updating the progress bar to whatever value you passed to it.

在响应之后,刷新输出缓冲区以确保这些数据被发送到浏览器。由于它是一个完整的脚本标记,浏览器将在其中执行javascript,将进度条更新为您传递给它的任何值。

To get the whole thing working you will need to add some calculations to make sense of the numbers you are calling to the progress bar, but I'm assuming that shouldn't be too much of a problem. It would probably make more sense to have setProgress in the example use percentages, though I wanted to stay clear of the calculations needed for the sake of clarity.

为了使整个工作顺利进行,您需要添加一些计算来理解您调用的进度条的数量,但是我认为这应该不会太麻烦。在示例使用百分比中设置setProgress可能会更有意义,尽管我想要清楚为了清晰起见所需要的计算。

#9


0  

Something seems fishy.

看起来可疑的东西。

I'm not familiar with databases that can't update 5000 records in a flash... So this isn't just a single update you're applying but a record by record update?

我不熟悉不能在一瞬间更新5000条记录的数据库……这不仅仅是你应用的一个更新而是一个记录一个记录更新?

Consider a system which lets a user download 5000 entries and does not mark which ones have been edited and then at the end of making updates applied a single update button which would require that all 5000 records be passed back in some way. That would be worst case.

考虑这样一个系统,它允许用户下载5000条条目,而不标记哪些条目被编辑过,然后在更新的最后应用一个更新按钮,该按钮将要求所有5000条记录以某种方式返回。那将是最坏的情况。

So there may be some way to partition the problem such that there is no wait time. Consider for instance a temporary db table (or just in the applications memory, pretty easy by just creating a list of ORM entities... but that's an aside) then when it comes time to commit those updates they can at least be done in a batch without requiring any client/server transfer. It may even be possible to mark the individual fields which were edited so there is nothing more updated on the DB except exactly what changed.

所以可能会有一些方法来划分问题,这样就没有等待时间了。例如,考虑一个临时的db表(或者仅仅是在应用程序内存中,很容易创建一个ORM实体的列表……但是这是一个旁白)然后当提交这些更新时,它们至少可以在不需要任何客户端/服务器传输的情况下批量完成。甚至可以标记被编辑的各个字段,这样除了更改的内容之外,DB上就没有其他更新了。

There are long running processes and I know sometimes you're stuck having to use what someone has provided you with... but perhaps with a bit of thinking you can simply get rid of the wait time.

有很长的运行过程,我知道有时候你不得不使用别人提供给你的东西……但是,也许你有一点想法,你可以简单地摆脱等待时间。

#10


0  

Create a simple table like this:

创建这样一个简单的表:

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

JQuery code:

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

Backend code (in PHP):

后端代码(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;

I have used this approach in 3 applications already and I must say it is very reliable and fast enogh (the showLog operation is just a simple SELECT statement). It is also possible to use session to store the progress, but that brings a lot of problems, since the session has to be write closed (if it is stored in files), otherwise the showLog AJAX queries will wait for the long operation to finish (and loose sense).

我已经在3个应用程序中使用了这种方法,我必须说它是非常可靠和快速的enogh (showLog操作只是一个简单的SELECT语句)。也可以使用会话来存储进度,但这带来了很多问题,因为会话必须被写入closed(如果它存储在文件中),否则showLog AJAX查询将等待长时间操作完成(并且感觉松散)。

#11


0  

I once did something similar this way (similar to Zain Shaikh, but simpler):

我曾经做过类似的事情(类似于Zain Shaikh,但更简单):

On the server

在服务器上

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

On the client
Follow the Zain Shaikh path but on ProcessInput simply resize your progress bar according to the ratio between the number of records to update and the input.length.

在客户机上遵循Zain Shaikh路径,但是在ProcessInput上,根据记录的数量和输入的长度之间的比率,简单地调整进度条的大小。

This solution often trades client side complexity for network bandwidth.

这个解决方案经常用客户端复杂性来交换网络带宽。

Do not mix this server activity with an eventual file import, unless you really know what you are doing. You would be trading db queries (the select for counting the updated rows) for stability: what if the user change page? There's no problem for the progress bar, but the import would not be completed.

不要将这个服务器活动与最终的文件导入混合在一起,除非您确实知道自己在做什么。您将交换db查询(用于计算更新行的选择)以获得稳定性:如果用户更改页面会怎样?进度条没有问题,但是导入不会完成。

#12


0  

For showing progress during load, I would modify my backend so that it can do selective loading.

为了显示加载过程中的进度,我将修改后端以便它可以进行选择性加载。

For example,

例如,

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

Every time the load is done, update the progress bar.

每次加载完成后,更新进度条。

An alternative way to modify the backend can be

另一种修改后端的方法是

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

#1


23  

The best I think is using Comet.

我认为最好的方法是使用Comet。

In Comet-style applications, the server can essentially push data to the client (instead of the client request data from the server again and again.). The client needs to only connect to server once. and then server will keep pushing data back to client.

在comet风格的应用程序中,服务器基本上可以将数据推送到客户机(而不是反复地从服务器请求客户机数据)。客户端只需要连接一次服务器。然后服务器会继续将数据推送到客户端。

From Wikipedia:

从*:

Comet is a programming technique that enables web servers to send data to the client without having any need for the client to request it. It allows creation of event-driven web applications which are hosted in the browser.

Comet是一种编程技术,它允许web服务器向客户机发送数据,而不需要客户机请求数据。它允许在浏览器中创建事件驱动的web应用程序。

And now let's see how Comet works. See the following server-side code. here a while loop is being used, you can instead set your own condition. In the while loop, the page writes a datetime and flushes and then sleeps for 1/2 seconds.

现在让我们看看Comet是如何工作的。请参阅下面的服务器端代码。这里使用了一个while循环,您可以设置自己的条件。在while循环中,页面写入一个datetime,刷新后休眠1/2秒。

ASP.NET page code-behind: Service.aspx.cs

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

Client side code - pure JavaScript

客户端代码——纯JavaScript

Only make the request once, and then keep checking the data in the readyState === 3 of XMLHttpRequest.

只请求一次,然后在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(); };

Reference

参考

#2


4  

I would let the function that is doing the big update record in a SESSION variable its current progress after each single (or so many) update, and use a separate AJAX script to retrieve this progress value from the SESSION and let JavaScript use this to update your progress bar/text.

我将让在会话变量中执行大更新记录的函数在每次(或多次)更新后执行当前进度,并使用单独的AJAX脚本从会话中检索此进度值,并让JavaScript使用它更新进度条/文本。

#3


2  

I am assuming you are currently using one POST for all records in the batch update and placing the loading image between call and return.

我假设您目前正在对批处理更新中的所有记录使用一个POST,并在调用和返回之间放置加载映像。

Rather than having the server wait to return until completing the update, have it return immediately with a special ID for that batch update. Then implement a server call that returns the status of the batch update, which your progress dialog can call to report the progress.

与其让服务器等待返回,直到完成更新,不如让它立即返回,并为该批更新提供一个特殊的ID。然后实现一个服务器调用,返回批处理更新的状态,您的进度对话框可以调用它来报告进度。

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

#4


2  

I would fire an Ajax callback once every n milliseconds that can query on how much is done (e.g. number of records updated) and use that to display a progress bar. Something like the way this works.

我将在每n毫秒的时间内触发一个Ajax回调,它可以查询完成了多少工作(例如更新的记录数量),并使用它来显示进度条。就像这样。

#5


2  

I just got an idea while reading the replies.

我在看回信的时候想到了一个主意。

JavaScript and PHP share cookies,

JavaScript和PHP共享cookie,

  1. Create a cookie with JavaScript when an Ajax call is made.
  2. 在生成Ajax调用时,用JavaScript创建一个cookie。
  3. In an Ajax PHP file increment the value in that cookie with each SQL update.
  4. 在Ajax PHP文件中,每次SQL更新时都要增加cookie中的值。
  5. In JavaScript a recursive function will read that particular cookie and will update progress bar numbers.
  6. 在JavaScript中,递归函数将读取特定的cookie并更新进度条号。

Advantages:

优点:

  1. Only 1 Ajax call.
  2. 只有一个Ajax调用。
  3. Less load on the server.
  4. 减少服务器上的负载。

#6


2  

You could update the response buffer with a progress, flushing your response buffer periodically from the server.

您可以使用进度更新响应缓冲区,定期从服务器刷新响应缓冲区。

But you may have trouble reading a request before it's complete via xhttpr. You might be able to make your request via an iframe, and have that load in progress via 'http streaming'.

但是在通过xhttpr完成请求之前,您可能会遇到读取请求的困难。您可能可以通过iframe发出请求,并通过“http流”进行加载。

But even that can be sketchy. HTTP is not the meant to transfer things piecemeal/fragmented. Like others point out, it would be best to make a separate subsequent calls to get the status of the operation.

但即使是这样也可能是粗略的。HTTP并不是用来传输零碎/片段的。就像其他人指出的那样,最好进行单独的后续调用,以获得操作的状态。

#7


1  

I am assuming that you have a reason for iterating through each record individually, instead of simply running a SQL statement.

我假设您有一个可以逐个遍历每个记录的理由,而不是简单地运行一个SQL语句。

If that is the case, simply make an ajax call every 200 or so iterations. If you do it for each group of 200 records, it will only consume 50 Ajax calls.

如果是这样,只需每大约200次迭代一次ajax调用。如果对每组200条记录都这样做,那么只会消耗50次Ajax调用。

Something like (pseudocode):

类似(伪代码):

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

#8


1  

I'm not sure what the server-side is where you are posting to, but you should be able to apply this method to most programming languages. I'm using PHP as an example.

我不确定要发布到的服务器端是什么,但是您应该能够将此方法应用到大多数编程语言中。我用PHP作为例子。

On the HTML-side, have some function that updates the progress bar to a given width. I'm calling this function 'setProgress' for this example, which takes a number to update the progress bar to.

在html端,有一些函数将进度条更新为给定的宽度。我为这个示例调用这个函数“setProgress”,它需要一个数字来更新进度条。

In the server-side code, do chunks of updates (say 100 per iteration) and generate output. By outputting a javascript call for each iteration:

在服务器端代码中,执行大量更新(比如每次迭代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!
?>

After echoing this, flush the output buffer to make sure this data is sent to the browser. Because it is a complete script tag, the browser will execute the javascript inside, updating the progress bar to whatever value you passed to it.

在响应之后,刷新输出缓冲区以确保这些数据被发送到浏览器。由于它是一个完整的脚本标记,浏览器将在其中执行javascript,将进度条更新为您传递给它的任何值。

To get the whole thing working you will need to add some calculations to make sense of the numbers you are calling to the progress bar, but I'm assuming that shouldn't be too much of a problem. It would probably make more sense to have setProgress in the example use percentages, though I wanted to stay clear of the calculations needed for the sake of clarity.

为了使整个工作顺利进行,您需要添加一些计算来理解您调用的进度条的数量,但是我认为这应该不会太麻烦。在示例使用百分比中设置setProgress可能会更有意义,尽管我想要清楚为了清晰起见所需要的计算。

#9


0  

Something seems fishy.

看起来可疑的东西。

I'm not familiar with databases that can't update 5000 records in a flash... So this isn't just a single update you're applying but a record by record update?

我不熟悉不能在一瞬间更新5000条记录的数据库……这不仅仅是你应用的一个更新而是一个记录一个记录更新?

Consider a system which lets a user download 5000 entries and does not mark which ones have been edited and then at the end of making updates applied a single update button which would require that all 5000 records be passed back in some way. That would be worst case.

考虑这样一个系统,它允许用户下载5000条条目,而不标记哪些条目被编辑过,然后在更新的最后应用一个更新按钮,该按钮将要求所有5000条记录以某种方式返回。那将是最坏的情况。

So there may be some way to partition the problem such that there is no wait time. Consider for instance a temporary db table (or just in the applications memory, pretty easy by just creating a list of ORM entities... but that's an aside) then when it comes time to commit those updates they can at least be done in a batch without requiring any client/server transfer. It may even be possible to mark the individual fields which were edited so there is nothing more updated on the DB except exactly what changed.

所以可能会有一些方法来划分问题,这样就没有等待时间了。例如,考虑一个临时的db表(或者仅仅是在应用程序内存中,很容易创建一个ORM实体的列表……但是这是一个旁白)然后当提交这些更新时,它们至少可以在不需要任何客户端/服务器传输的情况下批量完成。甚至可以标记被编辑的各个字段,这样除了更改的内容之外,DB上就没有其他更新了。

There are long running processes and I know sometimes you're stuck having to use what someone has provided you with... but perhaps with a bit of thinking you can simply get rid of the wait time.

有很长的运行过程,我知道有时候你不得不使用别人提供给你的东西……但是,也许你有一点想法,你可以简单地摆脱等待时间。

#10


0  

Create a simple table like this:

创建这样一个简单的表:

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

JQuery code:

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

Backend code (in PHP):

后端代码(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;

I have used this approach in 3 applications already and I must say it is very reliable and fast enogh (the showLog operation is just a simple SELECT statement). It is also possible to use session to store the progress, but that brings a lot of problems, since the session has to be write closed (if it is stored in files), otherwise the showLog AJAX queries will wait for the long operation to finish (and loose sense).

我已经在3个应用程序中使用了这种方法,我必须说它是非常可靠和快速的enogh (showLog操作只是一个简单的SELECT语句)。也可以使用会话来存储进度,但这带来了很多问题,因为会话必须被写入closed(如果它存储在文件中),否则showLog AJAX查询将等待长时间操作完成(并且感觉松散)。

#11


0  

I once did something similar this way (similar to Zain Shaikh, but simpler):

我曾经做过类似的事情(类似于Zain Shaikh,但更简单):

On the server

在服务器上

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

On the client
Follow the Zain Shaikh path but on ProcessInput simply resize your progress bar according to the ratio between the number of records to update and the input.length.

在客户机上遵循Zain Shaikh路径,但是在ProcessInput上,根据记录的数量和输入的长度之间的比率,简单地调整进度条的大小。

This solution often trades client side complexity for network bandwidth.

这个解决方案经常用客户端复杂性来交换网络带宽。

Do not mix this server activity with an eventual file import, unless you really know what you are doing. You would be trading db queries (the select for counting the updated rows) for stability: what if the user change page? There's no problem for the progress bar, but the import would not be completed.

不要将这个服务器活动与最终的文件导入混合在一起,除非您确实知道自己在做什么。您将交换db查询(用于计算更新行的选择)以获得稳定性:如果用户更改页面会怎样?进度条没有问题,但是导入不会完成。

#12


0  

For showing progress during load, I would modify my backend so that it can do selective loading.

为了显示加载过程中的进度,我将修改后端以便它可以进行选择性加载。

For example,

例如,

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

Every time the load is done, update the progress bar.

每次加载完成后,更新进度条。

An alternative way to modify the backend can be

另一种修改后端的方法是

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