2010-10-10 329 views
33

我有一个Ajax调用,可以更新数据库中的5,000条记录,所以这需要很多时间。我有一个Ajax的“加载图像”显示正在发生的事情,但我正在寻找一种更好的方式来显示“更新5000个5000 ...”,“更新5000个5000”或类似的东西。在Ajax调用中显示进度的最佳方式是什么?

在没有5000个不同的帖子的情况下,在Ajax/jQuery中做这样的事情的最佳方式是什么?

+2

什么是一些有趣的加载语句,以保持用户的逗乐? http://stackoverflow.com/questions/182112/what-are-some-funny-loading-statements-to-keep-users-amused – 2010-10-10 18:13:26

+2

[在长Ajax调用期间显示进度]可能的重复(http:// stackoverflow .com/questions/2572830/show-progress-during-a-long-ajax-call) – 2010-10-10 18:38:35

+2

@Amr - 但是那些不显示进度,只是正在加载内容。 – 2010-10-10 20:30:36

回答

23

我认为最好的是使用Comet

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

维基百科:

Comet是一种编程技术,使网络服务器将数据发送到客户端,而不需要任何客户端请求它。它允许创建托管在浏览器中的事件驱动的Web应用程序。

现在我们来看看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

只有发出请求一次,然后继续检查数据在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(); }; 

Reference

+0

小心......每个浏览器都会处理这个问题的方式有所不同 - 即它不起作用。猜测浏览器的行为与其他浏览器不同。有关更多详情,请参阅下面的答案。 – user406905 2010-12-27 19:32:30

1

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

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

类似的信息(伪):

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

我目前使用的是在批量更新中的所有记录一个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 
+0

这与Splunk的REST API(以及其他Web服务)的工作原理有些相似。 “Jobs”作为一个粗略的例子 - http://www.splunk.com/base/Documentation/latest/Developer/RESTSearch – 2010-10-10 18:29:42

+0

http://en.wikipedia.org/wiki/Comet_%28programming%29(来自http:/ /stackoverflow.com/questions/2572830/show-progress-during-a-long-ajax-call) – 2010-10-10 21:17:02

4

我先给是每个单个(或很多)更新后做的大更新记录在会话变量的当前进度的功能,并使用单独的AJAX脚本从session中获取此进度值让JavaScript使用它来更新你的进度条/文本。

+0

这听起来有点乱。 。你有任何这样做的例子? – leora 2010-12-14 04:06:05

2

我会每隔n发出一次Ajax回调,它可以查询完成了多少操作(例如更新的记录数)并使用它来显示进度条。类似于this的工作方式。

2

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

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

但即使这可能是粗略的。 HTTP并不是意味着零散/零碎的东西。像其他人一样指出,最好是单独进行后续调用以获取操作状态。

0

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

例如,

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 
2

我只是有一个想法,而读的答复。

JavaScript和PHP份额饼干,

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

优点:

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

哇可能吗?在没有多个Ajax调用的情况下,cookie的变化值如何在客户端(javascript)和服务器之间传递? – 2010-12-17 21:07:32

+0

javascript会检查cookie值与递归读取cookie功能,它不会使用ajax。 http://www.w3schools.com/js/js_cookies.asp检查此链接以阅读使用javascript的cookie – 2010-12-20 06:36:04

+0

在客户端和服务器之间必须存在一些内部握手以供cookie更新反映。可能我们能够改变服务器端的cookie值,并在客户端读取它,因为这种内部通信。没有这个,怎么可能? – 2010-12-20 10:12:46

0

东西似乎很腥。

我不熟悉无法在闪存中更新5000条记录的数据库...因此,这不仅仅是您正在应用的单个更新,还是通过记录更新创建的记录?

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

因此,可能有某种方法来分割问题,以便不会有等待时间。考虑一个临时数据库表(或者只是在应用程序内存中,通过创建一个ORM实体列表非常简单,但这只是一个问题),那么当提交这些更新时,他们至少可以在不需要任何客户端/服务器传输。甚至可以标记编辑过的各个字段,因此除了刚才发生了什么变化之外,没有更多的数据库更新。

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

1

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

0

创建一个简单的表是这样的:

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语句)。也可以使用会话来存储进度,但会带来很多问题,因为会话必须写入关闭(如果它存储在文件中),否则showLog AJAX查询将等待长时间操作完成(和松散的感觉)。

+0

您的代码实际上是每次轮询服务器。 OP不需要轮询,正如他明确提到的*'不做5000个不同的职位'* – 2010-12-20 08:56:22

+0

那么,如果运行4个小时,这段代码将向服务器发出5000个请求。在10分钟的操作中,这将提出150个请求,这与5000个相差很远。他没有说他希望在一个请求中使用它。 – Odin 2010-12-21 08:26:44

0

我曾经做过类似的东西这种方式(类似于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 Shaikh的路径,但是ProcessInput根据要更新的记录数与input.length之间的比率,简单地调整进度栏的大小。

该解决方案通常针对网络带宽交易客户端复杂性。

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

相关问题