提问人:Madara's Ghost 提问时间:8/13/2011 最后编辑:JohnMadara's Ghost 更新时间:1/29/2021 访问量:92773
显示进度元素的 AJAX 上传状态
Show AJAX upload status on progress element
问:
我有一个 AJAX 脚本,用于将文件上传到可能需要至少 10 秒才能运行的 PHP 脚本。我想为用户显示它的进度。
在执行类中,我有一个属性,该属性会随着进度(以 1-100 为单位)和一个方法(其目的应该很明显)。$progress
get_progress()
问题是,如何更新前端的元素供用户查看?<progress>
我认为 AJAX 是解决方案,但我就是无法理解它。我无法访问相同的对象实例。
答:
这有点困难,(仅供参考)PHP 进程和您的 AJAX 请求由单独的线程处理,因此您无法获得该值。$progress
快速解决方案:您可以在每次更新时写入进度值,然后您的 AJAX 请求可以通过访问 .$_SESSION['some_progress']
$_SESSION['some_progress']
您将需要 JavaScript 的 or 来继续调用 AJAX 处理程序,直到您得到 .setInterval()
setTimeout()
100
这不是完美的解决方案,但它快速而简单。
由于不能同时使用同一会话两次,因此请改用数据库。将状态写入数据库,并使用间隔的 AJAX 调用从中读取。
评论
$_SESSION
session_start()
您是否考虑过输出 javascript 并使用流刷新?它看起来像这样
echo '<script type="text/javascript> update_progress('.($progress->get_progress()).');</script>';
flush();
由于刷新,此输出会立即发送到浏览器。从长时间运行的脚本中定期执行此操作,它应该可以很好地工作。
评论
我会把它放在这里作为任何搜索的参考 - 这根本不依赖于 javascript..
<?php
/**
* Quick and easy progress script
* The script will slow iterate through an array and display progress as it goes.
*/
#First progress
$array1 = array(2, 4, 56, 3, 3);
$current = 0;
foreach ($array1 as $element) {
$current++;
outputProgress($current, count($array1));
}
echo "<br>";
#Second progress
$array2 = array(2, 4, 66, 54);
$current = 0;
foreach ($array2 as $element) {
$current++;
outputProgress($current, count($array2));
}
/**
* Output span with progress.
*
* @param $current integer Current progress out of total
* @param $total integer Total steps required to complete
*/
function outputProgress($current, $total) {
echo "<span style='position: absolute;z-index:$current;background:#FFF;'>" . round($current / $total * 100) . "% </span>";
myFlush();
sleep(1);
}
/**
* Flush output buffer
*/
function myFlush() {
echo(str_repeat(' ', 256));
if (@ob_get_contents()) {
@ob_end_flush();
}
flush();
}
?>
评论
sleep(1)
echo(str_repeat(' ', 256));
echo str_repeat(' ', 4*1024), "\n";
解决方案是:
Ajax 轮询 - 在服务器端将进度存储在某个位置,然后使用 ajax 调用定期获取进度。
服务器发送的事件 - 一个 html5 功能,允许从服务器发送的输出生成 dom 事件。这是这种情况的最佳解决方案,但 IE 10 不支持它。
脚本/iframe 流式处理 - 使用 iframe 流式传输长时间运行的脚本的输出,该脚本会将脚本标记输出为可在浏览器中生成一些反应的时间间隔。
这是一个老问题,但我也有类似的需求。我想用php命令运行一个脚本并显示输出。system()
我在没有轮询的情况下做到了。
对于第二个Rikudoit案例应该是这样的:
JavaScript的
document.getElementById("formatRaid").onclick=function(){
var xhr = new XMLHttpRequest();
xhr.addEventListener("progress", function(evt) {
var lines = evt.currentTarget.response.split("\n");
if(lines.length)
var progress = lines[lines.length-1];
else
var progress = 0;
document.getElementById("progress").innerHTML = progress;
}, false);
xhr.open('POST', "getProgress.php", true);
xhr.send();
}
PHP的
<?php
header('Content-Type: application/octet-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
// Get the curent level
$level = ob_get_level();
// End the buffering
ob_end_clean();
// If the current level has not changed, abort
if (ob_get_level() == $level) break;
}
while($progress < 100) {
// STUFF TO DO...
echo '\n' . $progress;
}
?>
评论
在Web应用程序中,我们经常会向后端系统发出请求,这可能会触发长时间运行的进程,例如搜索大量数据或长时间运行的数据库进程。然后前端网页可能会挂起并等待该过程完成。在这个过程中,如果我们能给用户提供一些关于后端进程进度的信息,可能会改善用户体验。不幸的是,在 Web 应用程序中,这似乎不是一件容易的事,因为 Web 脚本语言不支持多线程,而 HTTP 是无状态的。我们现在可以使用 AJAX 来模拟实时过程。
基本上,我们需要三个文件来处理请求。第一个是运行实际长时间运行作业的脚本,它需要有一个会话变量来存储进度。第二个脚本是状态脚本,它将回显长时间运行的作业脚本中的会话变量。最后一个是客户端 AJAX 脚本,它可以频繁轮询状态脚本。
具体实现可以参考PHP动态获取长时间运行的进程进度
如果你的任务是上传一个巨大的数据集或在服务器上处理它,在更新服务器的进度时,你应该考虑使用某种作业架构,你启动作业并使用服务器上运行的其他脚本来完成它(例如缩放/处理图像等)。在这种情况下,您一次只做一件事,从而形成一个任务管道,其中有一个输入和一个最终处理的输出。
在管道的每个步骤中,任务的状态都会在数据库内更新,然后可以通过目前存在的任何服务器推送机制将其发送给用户。运行一个处理上传和更新的脚本会给你的服务器带来负载,也会限制你(如果浏览器关闭怎么办,如果发生其他错误怎么办)。当流程被划分为多个步骤时,您可以从上次成功的位置恢复失败的任务。
有很多方法可以做到这一点。但整个流程如下所示
以下方法是我为个人项目所做的,该脚本适用于将数千张高分辨率图像上传和处理到我的服务器,然后将其缩小到多个版本并上传到 amazon s3,同时识别其中的对象。(我的原始代码是python)
步骤 1 :
启动传输或任务
首先上传您的内容,然后通过简单的 POST 请求立即返回此交易的交易 ID 或 uuid。如果要在任务中执行多个文件或多个操作,则可能还需要在此步骤中处理该逻辑
步骤2:
完成工作并返回进度。
一旦你弄清楚了事务是如何发生的,你就可以使用任何服务器端推送技术来发送更新数据包。我会选择WebSocket或Server Sent Events,以适用的方式回退到不受支持的浏览器上的长轮询。一个简单的 SSE 方法如下所示。
function TrackProgress(upload_id){
var progress = document.getElementById(upload_id);
var source = new EventSource('/status/task/' + upload_id );
source.onmessage = function (event) {
var data = getData(event); // your custom method to get data, i am just using json here
progress.setAttribute('value', data.filesDone );
progress.setAttribute('max', data.filesTotal );
progress.setAttribute('min', 0);
};
}
request.post("/me/photos",{
files: files
}).then(function(data){
return data.upload_id;
}).then(TrackProgress);
在服务器端,您将需要创建一些跟踪任务的东西,一个简单的作业架构,其中包含发送到数据库的job_id和进度就足够了。我会把作业调度和路由留给你,但在那之后,概念代码(对于最简单的 SSE,上面的代码就足够了)如下。
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
/* Other code to take care of how do you find how many files are left
this is really not required */
function sendStatusViaSSE($task_id){
$status = getStatus($task_id);
$json_payload = array('filesDone' => $status.files_done,
'filesTotal' => $status.files_total);
echo 'data: ' . json_encode( $json_payload ) . '\n\n';
ob_flush();
flush();
// End of the game
if( $status.done ){
die();
}
}
while( True ){
sendStatusViaSSE( $request.$task_id );
sleep(4);
}
?>
可以在此处找到有关 SSE 的优秀教程 http://html5doctor.com/server-sent-events/
您可以在此问题中阅读有关从服务器推送更新的更多信息 从服务器推送更新
以上是一个概念性的解释,还有其他方法可以实现这一点,但这是我处理一个相当艰巨任务的解决方案。
评论
在这里,我只想在 @Jarod Law 上面写的内容之上添加 2 个问题 https://stackoverflow.com/a/7049321/2012407
确实非常简单和高效。我调整并使用了:) 所以我的两个担忧是:
而不是在回调中使用或使用递归调用,例如:
setInterval()
setTimeout()
function trackProgress() { $.getJSON(window.location, 'ajaxTrackProgress=1', function(response) { var progress = response.progress; $('#progress').text(progress); if (progress < 100) trackProgress();// You can add a delay here if you want it is not risky then. }); }
由于调用是异步的,否则可能会以不需要的顺序返回。
保存到是智能的,你可以,不需要数据库存储。 您实际需要的是允许同时调用两个脚本,而不是被 PHP 排队。所以你最需要的是
session_write_close()
! 我在这里发布了一个非常简单的演示示例: https://stackoverflow.com/a/38334673/2012407$_SESSION['some_progress']
这是通过 和 进行的最有效、最简单和经过测试的解决方案。$_SESSION['progress']
session_start()
session_write_close()
这个想法是,我们将保存进度,然后锁定会话以不断向用户更新进度。
$_SESSION['progress']
session_write_close()
在迭代开始时和迭代结束时使用。
session_start();
session_write_close()
然后通过 ajax 从不同的脚本中获取此会话变量值。
$_SESSION['progress']
然后通过 progress_bar 中的 ajax 响应等显示结果。
现在让我们做一些代码:
创建一个表单/请求页面以触发 AJAX 请求 (request.php):
<div id="demo">
<button type="button" class="button">Do Something</button>
</div>
$("button").click(function(){
$.ajax({url: "request-handler.php", success: function(result){
alert('done');
}});
});
请求处理程序.php
$total = 100;
$count = 1;
while(true)
{
/*write your code
....
here*/
session_start();
$percent = ($count *100)/$total;
$_SESSION["progress"] = number_format((float)$percent, 2, '.', '');
$_SESSION["progress"] = (int) ceil($percent);
$count++;
session_write_close();
}
Ajax 获得进度(前视图 .php):
<div class="progress" style="display: none;">
<div class="progress-bar progress-bar-striped active" role="progressbar"
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:0%">
0%
</div>
</div>
<div class="alert alert-success alert-dismissible" style="display: none;">
<a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>
<strong>Done!</strong> Successfully Done!.
</div>
<script type="text/javascript">
var pBar = $(".progress-bar");
var progress = 0;
var intervalId = window.setInterval(function(){
$.get("get-progress.php", function(data, status){
progress = data;
});
if(progress > 0)
{
pBar.parent().show();
}
var percent = progress+'%';
pBar.width(percent);
pBar.html(percent);
if(progress >= 100)
{
pBar.parent().hide();
$ (".alert").show();
clearInterval(intervalId);
}
}, 2000);
</script>
获取进度.php:
<?php
session_start();
$progress = $_SESSION['progress'];
if ($progress >= 100) {
session_start();
unset($_SESSION['progress']);
}
//output the response
echo json_encode($progress);
?>
评论