PHP/JS进度条

Kie*_*ran 7 javascript php ajax jquery server-sent-events

我正在尝试创建一个页面,它将从复杂的数据库查询和php解析生成结果集......但这主要是在这一点上......主要的一点是,这需要一两分钟才能完成,并且我希望显示一个进度条而不是一个通用的gif动画"loading ..."图片.

故障将是......

  • 用户打开页面A.
  • 页面A请求来自页面B的数据(很可能是AJAX).
  • 页面B处理数据库中的100000多个条目并解析它们.
  • 页面A显示了一个进度条,大致显示了该过程的进度
  • 页面B返回结果集.
  • 页面A显示结果集.

我知道如何将数据返回到ajax查询,但我的问题是我不知道如何连续返回数据以显示进程的状态(例如,扫描的行数百分比).

我查看了EventSource/Server-Sent-Events,它显示了承诺,我只是不太确定如何使其正常工作,或者是否有更好的方法来实现它.

我已经尝试制作一个快速的小模型页面,只使用EventSource工作正常,但当我将其拆分为eventSource调用(监视会话变量以进行更改的页面)和ajax请求(实际数据发送/它崩溃了.

我可能错过了一些明显的东西,或做了一些愚蠢的错误,但这是我所拥有的大部分内容......任何帮助,建议,提示,甚至完全其他方法的建议都会很棒:)

用户页面:

<!DOCTYPE html>
<html>
<head>
    <title>Dynamic Progress Bar Example</title>
    <script src="script.js"></script>
</head>
<body>
    <input type="button" value="Submit" onclick="connect()" />
    <progress id='progressor' value="0" max='100' style=""></progress>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

使用Javascript

 var es;

   function connect() {
       startListener();
       $.ajax({
           url: "server.php",
           success: function() {
               alert("Success");
           },
           error: function() {
               alert("Error");
           }
       });
   }

   function startListener() {
       es = new EventSource('monitor.php');

       //a message is received
       es.addEventListener('message', function(e) {
           var result = JSON.parse(e.data);

           if (e.lastEventId == 'CLOSE') {
               alert("Finished!");
               es.close();
           } else {
               var pBar = document.getElementById('progressor');
               pBar.value = result;
           }
       });

       es.addEventListener('error', function(e) {
           alert('Error occurred');
           es.close();
       });
   }

   function stopListener() {
       es.close();
       alert('Interrupted');
   }

   function addLog(message) {
       var r = document.getElementById('results');
       r.innerHTML += message + '<br>';
       r.scrollTop = r.scrollHeight;
   }
Run Code Online (Sandbox Code Playgroud)

监控PHP

<?php
SESSION_START();
header('Content-Type: text/event-stream');
// recommended to prevent caching of event data.
header('Cache-Control: no-cache'); 

function send_message($id, $data) {
    $d = $data;
    if (!is_array($d)){
        $d = array($d);
    }

    echo "id: $id" . PHP_EOL;
    echo "data: " . json_encode($d) . PHP_EOL;
    echo PHP_EOL;

    ob_flush();
    flush();
}


$run = true;
$time = time();
$last = -10;

while($run){
    // Timeout kill checks
    if (time()-$time > 360){
        file_put_contents("test.txt", "DEBUG: Timeout Kill", FILE_APPEND);
        $run = false;
    }

    // Only update if it's changed
    if ($last != $_SESSION['progress']['percent']){
        file_put_contents("test.txt", "DEBUG: Changed", FILE_APPEND);
        $p = $_SESSION['progress']['percent'];
        send_message(1, $p); 
        $last = $p;
    }

    sleep(2);
}
?>
Run Code Online (Sandbox Code Playgroud)

编辑:我尝试了不同的方法,其中:

  • 页面A AJAX调用运行请求的页面B,并将进度保存到SESSION变量
  • 页面A AJAX每2秒调用一次页面C,它只返回会话变量的值.当该循环达到100时终止

但是,这也不是很有效.似乎两个AJAX请求或服务器端的两个脚本没有同时运行.

查看调试输出:两个AJAX调用几乎同时执行,但随后页面B脚本自行运行完成,然后 - 然后 - 页面C脚本运行.这是PHP的一些限制我错过了???

更多代码!

服务器(页面B)PHP

<?PHP
    SESSION_START();

    file_put_contents("log.log", "Job Started\n", FILE_APPEND);

    $job = isset($_POST['job']) ? $_POST['job'] : 'err_unknown';
    $_SESSION['progress']['job'] = $job;
    $_SESSION['progress']['percent'] = 0;

    $max = 10;
    for ($i=0; $i<=$max;$i++){
        $_SESSION['progress']['percent'] = floor(($i/$max)*100);
        file_put_contents("log.log", "Progress now at " . floor(($i/$max)*100) . "\n", FILE_APPEND);
        sleep(2);
    }

    file_put_contents("log.log", "Job Finished", FILE_APPEND);
    echo json_encode("Success. We are done.");
?>
Run Code Online (Sandbox Code Playgroud)

进展(第C页)PHP

<?php
    SESSION_START();
    file_put_contents("log.log", "PR: Request Made", FILE_APPEND);

    if (isset($_SESSION['progress'])){
        echo json_encode(array("job"=>$_SESSION['progress']['job'],"progress"=>$_SESSION['progress']['percent']));
    } else {
        echo json_encode(array("job"=>"","progress"=>"error"));
    }
?>
Run Code Online (Sandbox Code Playgroud)

索引(页面A)JS/HTML

<!DOCTYPE html>
<html>
<head>
        <title>Progress Bar Test</title>
</head>
<body>
        <input type="button" value="Start Process" onclick="start('test', 'pg');"/><br />
        <progress id="pg" max="100" value="0"/>

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <script type="text/javascript">
            var progress = 0;
            var job = "";

            function start(jobName, barName){
                startProgress(jobName, barName);
                getData(jobName);
            }

            function getData(jobName){
                console.log("Process Started");
                $.ajax({
                    url: "server.php",
                    data: {job: jobName},
                    method: "POST",
                    cache: false,
                    dataType: "JSON",
                    timeout: 300,
                    success: function(data){
                        console.log("SUCCESS: " + data)
                        alert(data);
                    },
                    error: function(xhr,status,err){
                        console.log("ERROR: " + err);
                        alert("ERROR");
                    }
                });
            }

            function startProgress(jobName, barName){
                console.log("PG Process Started");
                progressLoop(jobName, barName);
            }

            function progressLoop(jobName, barName){
                console.log("Progress Called");
                $.ajax({
                    url: "progress.php",
                    cache: false,
                    dataType: "JSON",
                    success: function(data){
                        console.log("pSUCCESS: " . data);
                        document.getElementById(barName).value = data.progress;
                        if (data.progress < 100 && !isNaN(data.progress)){
                            setTimeout(progressLoop(jobName, barName), (1000*2));
                        }
                    },
                    error: function(xhr,status,err){
                        console.log("pERROR: " + err);
                        alert("PROGRESS ERROR");
                    }
                });
            }
        </script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

Debug:log.log输出

PR: Request Made
Job Started
Progress now at 0
Progress now at 10
Progress now at 20
Progress now at 30
Progress now at 40
Progress now at 50
Progress now at 60
Progress now at 70
Progress now at 80
Progress now at 90
Progress now at 100
Job Finished
PR: Request Made
Run Code Online (Sandbox Code Playgroud)

Kir*_*voy 2

遇到类似的情况,我通常是这样处理的:

  • 客户端向页面 B 发送 AJAX 请求。 重要提示:成功后,客户端会再次发送相同的请求。
  • 对于最初的请求,B 页说:OK, THERE ARE 54555 RECORDS.。我用这个计数来启动进度条。
  • 对于接下来的每个请求,页面 B 都会返回一块数据。客户端计算块的大小并更新进度条。它还将块收集在一个列表中。
  • 在最后一个请求中,当发送所有数据时,页面 B 会显示:THAT'S ALL并且客户端呈现数据。

我想,你已经明白了。

注意:您可以并行请求所有块,但这是一种复杂的方式。服务器(页面B)还应该在初始响应中返回固定的块大小,然后客户端并发发送TOTAL_COUNT / CHUNK_SIZE请求并合并响应,直到最后一个请求完成。所以速度要快得多。在这种情况下,您可以使用https://github.com/caolan/async来使代码更具可读性。