在Ajax调用中显示进度的最佳方式是什么?

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

在Ajax / jQuery中做这样的事情的最佳方式是什么?

我认为最好的是使用彗星 。

在Comet风格的应用程序中,服务器本质上可以将数据推送到客户端(而不是一次又一次地从服务器端发送客户端请求数据)。 客户端只需连接到服务器一次 。 然后服务器将继续推送数据回到客户端。

维基百科:

彗星是一种编程技术,它使Web服务器能够向客户端发送数据,而不需要客户端请求它。 它允许创build在浏览器中托pipe的事件驱动的Web应用程序。

现在让我们看看Comet如何工作。 请参阅以下服务器端代码。 这里正在使用while循环,您可以改为设置自己的状态。 在while循环中,页面写入date时间并刷新,然后hibernate1/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

只做一次请求,然后检查XMLHttpRequestreadyState === 3中的数据。

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

参考

我会让在SESSION中执行大更新logging的函数在每次(或那么多次)更新之后variables当前的进度,并使用单独的AJAX脚本从SESSION中检索这个进度值,并让JavaScript使用它来更新你的进度条/文本。

我假设你正在使用批处理更新中的所有logging一个POST并将调用和返回之间的加载图像。

在完成更新之前,不要让服务器等待返回,而是立即返回该批次更新的特殊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 

我会每n毫秒发一次Ajaxcallback,可以查询完成了多less(例如更新的logging数),并使用它来显示进度条。 像这样的工作方式。

我刚刚读到回复时有个想法。

JavaScript和PHP共享cookie,

  1. 在进行Ajax调用时,使用JavaScript创build一个cookie。
  2. 在Ajax PHP文件中,使用每个SQL更新来增加该cookie中的值。
  3. 在JavaScript中,recursion函数将读取特定的cookie,并将更新进度条编号。

优点:

  1. 只有1个Ajax调用。
  2. 服务器负载较轻。

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

但是,在通过xhttpr完成请求之前,您可能无法读取请求。 你可能可以通过iframe发出请求,并通过“httpstream”来加载。

但即使如此,也可能是粗略的。 HTTP并不意味着将东西零碎/碎片化。 像其他人一样指出,最好是单独的后续调用来获得操作的状态。

我假设你有一个单独遍历每条logging的原因,而不是简单地运行一条SQL语句。

如果是这样的话,只需每隔200次迭代就调用一次ajax。 如果您为每组200条logging执行此操作,则只会消耗50个Ajax调用。

像(伪代码):

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

我不确定服务器端是在哪里发布的,但是您应该能够将这种方法应用于大多数编程语言。 我使用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可能更有意义,但为了清晰起见,我想避开所需的计算。

东西似乎很腥。

我不熟悉无法在闪存中更新5000条logging的数据库…因此,这不仅仅是一个更新,而是一个logging更新的logging?

考虑一个系统,它允许用户下载5000个条目并且不标记哪些已经被编辑,然后在更新结束时应用单个更新button,这将需要所有5000条logging以某种方式传回。 那将是最坏的情况。

所以可能有一些方法来分割这个问题,使得没有等待时间。 考虑一个临时数据库表(或者只是在应用程序内存中,通过创build一个ORM实体列表非常容易…但是这是一个问题),那么当提交这些更新时,他们至less可以在不需要任何客户端/服务器传输。 甚至可以标记已编辑的单个字段,因此除了完全改变之外,DB上没有任何更新。

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

像这样创build一个简单的表格:

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

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

在服务器上

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

在客户端
遵循Zain Shaikhpath,但ProcessInput只是根据要更新的logging数与input.length之间的比率来调整进度条的input.length

这个解决scheme往往会为客户端带来复杂的networking带宽。

除非你真的知道你在做什么,否则不要将这个服务器活动与最终的文件导入混合在一起。 你会交易数据库查询(select计数更新的行)的稳定性:如果用户更改页面? 进度条没有问题,但导入不会完成。

为了显示加载过程中的进度,我会修改我的后端,以便它可以做select性加载。

例如,

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

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

另一种修改后端的方法可以是

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