我有一个Ajax调用,可以更新数据库中的5,000条记录,因此这需要花费很多时间。我有一个Ajax“正在加载图片”,显示正在发生的事情,但我正在寻找一种更好的方式来显示“正在更新50的5000 ...”,“更新200的5000”,或类似的东西。
在没有5000个不同帖子的情况下,在Ajax / jQuery中做这样的事情的最佳方法是什么?
答案 0 :(得分:23)
我认为最好的是使用Comet。
在Comet风格的应用程序中,服务器实际上可以将数据推送到客户端(而不是一次又一次地从服务器请求客户端请求数据)。客户端只需连接到服务器一次。然后服务器将继续将数据推送回客户端。
来自维基百科:
Comet是一种编程技术,它使Web服务器能够将数据发送到客户端,而无需客户端请求它。它允许创建托管在浏览器中的事件驱动的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
仅提出一次请求,然后继续检查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(); };
答案 1 :(得分:4)
我会让在SESSION变量中执行大更新记录的函数在每次单个(或那么多)更新后进行当前进度,并使用单独的AJAX脚本从SESSION中检索此进度值并让JavaScript使用这样可以更新进度条/文本。
答案 2 :(得分: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
答案 3 :(得分:2)
每隔 n 毫秒,我会触发一次Ajax回调,可以查询完成了多少(例如更新的记录数)并使用它来显示进度条。类似于this的工作方式。
答案 4 :(得分:2)
您可以使用进度更新响应缓冲区,定期从服务器刷新响应缓冲区。
但您可能无法通过xhttpr
在完成请求之前阅读该请求。您可以通过iframe发出请求,并通过“http streaming”进行加载。
但即使这样也可以粗略。 HTTP并不意味着将零碎的东西转移到碎片上。像其他人指出的那样,最好进行单独的后续调用以获取操作的状态。
答案 5 :(得分:2)
我在阅读回复时得到了一个主意。
JavaScript和PHP共享cookie,
优点:
答案 6 :(得分:1)
我假设您有理由单独遍历每条记录,而不是简单地运行SQL语句。
如果是这种情况,只需每200次左右进行一次ajax调用。如果对每组200条记录执行此操作,它将只消耗50个Ajax调用。
像(伪代码):
If iterationNumber mod 200 == 0
// Make Ajax call.
答案 7 :(得分: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可能更有意义使用百分比,尽管为了清晰起见我想要清除所需的计算。
答案 8 :(得分: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
答案 9 :(得分:0)
有些东西似乎很可疑。
我不熟悉无法在闪存中更新5000条记录的数据库......所以这不仅仅是您应用的单个更新,而是记录更新的记录?
考虑一个允许用户下载5000个条目并且不标记哪些条目已被编辑的系统,然后在更新结束时应用单个更新按钮,这将要求以某种方式传回所有5000条记录。那将是最糟糕的情况。
因此可能有某种方法可以对问题进行分区,使得没有等待时间。考虑一个临时的数据库表(或者只是在应用程序内存中,只需创建一个ORM实体列表就很容易......但是这是一个旁边的)然后当提交这些更新时,它们至少可以在一个批量不需要任何客户端/服务器传输。甚至可以标记已编辑的各个字段,这样除了确切改变之外,DB上没有更新的内容。
有很长的运行过程,我知道有时候你不得不使用别人提供给你的东西......但也许有点想到你可以简单地摆脱等待时间。
答案 10 :(得分: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语句)。也可以使用session来存储进度,但这会带来很多问题,因为会话必须写入关闭(如果它存储在文件中),否则showLog AJAX查询将等待长操作完成(和松散的感觉)。
答案 11 :(得分: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
之间的比率调整进度条的大小。
此解决方案通常会将客户端复杂性换成网络带宽。
请勿将此服务器活动与最终的文件导入混合,除非您确实知道自己在做什么。 您将交换数据库查询(用于计算更新行的选择)以获得稳定性:如果用户更改页面该怎么办?进度条没有问题,但导入将无法完成。