javascript - 尝试异步 Javascript,无法正常工作。更新
问题描述
更新:我对 JS 承诺没有完全理解,所以我删除了该代码,并添加了一个通过 AJAX 而不是脚本注入启动服务器进程的选项,但除非我在服务器上分叉 php 进程,否则它仍然会阻塞。我已将下面的代码块替换为文件的更新版本,它允许所有四种方法(脚本注入或 AJAX,以及分叉或非分叉)。要运行它,需要一个包含 pcntl 模块的 php 构建(最新的 homebrew 可访问的似乎是 5.6)。
这是一个完整的 php 脚本,用于使用 HTML5 和 CSS 实现为异步 javascript 进度条生成测试页面。测试运行没有错误,但我无法让它加载 progress.js 文件并运行它,更新进度条。间隔终止后控制台会更新,但在此期间浏览器中没有任何反应。在当前版本的 Firefox 和 Chrome 中进行了测试,结果相同。
注意:在生成 HTML 的代码中使用 heredoc 语法只是为了方便代码折叠,因为我的编辑器不能为此目的在同一文档中识别 php 和 javascript 语法。
<?php // UPDATED: 2020.05.06 - 12.36
if( isset($_SERVER['QUERY_STRING']) ){
$qry = $_SERVER['QUERY_STRING'];
if( $qry == 'pi' ) phpinfo();
else{
file_put_contents( 'progress.js', '' );
header('Content-Type: text/javascript');
if( $qry == 'fork' ){
if( function_exists('pcntl_fork') ) $pid = pcntl_fork();
else $pid = false;
if( $pid === -1 ) die('could not fork');
else if( $pid > 0 ) file_put_contents('__fork_pid', $pid);
else if( $pid === 0 ){
prog('timer = 0');
for( $x = 0; $x < 101; $x++ ){ // Simulate a task taking an indeterminate amount of time
$s = rand(0,10) * 25000; // E.g. copying a set number of files
usleep( $s ); // Timer value would reflect percentage of file transfers completed
prog("timer = $x"); // Update the timer after each file is copied
}
if( file_exists('__fork_pid') ){
$pid = trim( file_get_contents('__fork_pid') );
unlink('__fork_pid');
`kill $pid`;
}
}
else echo "console.log('fork failed!'); progress(0);";
}
else{
prog('timer = 0');
for( $x = 0; $x < 101; $x++ ){
$s = rand(0,10) * 25000;
usleep( $s );
prog("timer = $x");
}
}
}
}
else{
echo /* HTML OPEN */ (<<<ENDINDEX
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Async Progress Bar Test</title>
<script>
var prgs, timer = 0;
ENDINDEX
).chr(10)
/* JS doAsyncTask */.(<<<ENDINDEX
function doAsyncTask(fork){
document.querySelector('#status').textContent = 'Running...';
setTimeout( () => {
progress(0);
if( fork < 2 ) scload('index.php?'+(fork==1?'fork':'nofork'));
else doAJAX(fork==3?'fork':'nofork');
}, 100, );
prgs = setInterval( () => {
progress(timer++); scload('progress.js');
}, 250 );
return 'doing asynchronous task...';
}
ENDINDEX
).chr(10)
/* JS doAJAX */ .(<<<ENDINDEX
function doAJAX(opt){ // This method appears NOT to be non-blocking, so fails.
ajax = new XMLHttpRequest();
ajax.open('GET', 'index.php?'+opt, true);
ajax.send();
ajax.onreadystatechange = function(){ if (ajax.readyState == 4) progress(ajax.responseText) };
}
ENDINDEX
).chr(10)
/* JS scload */ .(<<<ENDINDEX
function scload(src){
if(document.querySelector('#scriptloader'))
document.querySelector('#scriptloader').remove();
let s = document.createElement('script');
s.id = 'scriptloader';
s.setAttribute('async',true);
s.src = src;
document.body.appendChild(s);
}
ENDINDEX
).chr(10)
/* JS progress */ .(<<<ENDINDEX
function progress(t=false){
if(t!==false && parseInt(t)>=0) timer = t;
document.querySelector('#progress').classList.toggle('hide',false);
document.querySelector('#status').textContent = 'Running... '+timer+'%';
if( timer >= 100 ){
clearInterval(prgs);
document.querySelector('#status').textContent = 'Complete.';
document.querySelector('#progress').classList.toggle('hide',true);
timer = 0;
}
document.querySelector('#progress span').style.width = timer+'%';
}
ENDINDEX
).chr(10)
/* CSS STYLE */ .(<<<ENDINDEX
</script>
<style>
body { background: #fff; }
button { display: inline-block; margin: 20px; width: 200px; height: 24px; font-weight: bold;
background: #eee; border-radius: 8px; }
button:active { background: #ccc; }
#progress { position: relative; width: 450px; height: 20px; background: #aaa; margin: 20px;
border-radius: 10px; border: 1px inset #000; box-sizing: border-box; overflow: hidden; }
#progress > span { position: absolute; height: 100%; background: #88f;
box-sizing: border-box; border-radius: 10px 0 0 10px; }
#status { margin: 20px; }
</style>
</head>
ENDINDEX
).chr(10)
/* HTML BODY */ .(<<<ENDINDEX
<body>
<button onclick="doAsyncTask(1)">Start SCLOAD (fork)</button>
<button onclick="doAsyncTask(0)">Start SCLOAD (no fork)</button> <br>
<button onclick="doAsyncTask(3)">Start AJAX (fork)</button>
<button onclick="doAsyncTask(2)">Start AJAX (no fork)</button>
<div id="status"> </div>
<div id="progress"><span></span></div>
</body>
</html>
ENDINDEX
);
}
function prog($do,$complete=false){
$action = "$do; \n";
file_put_contents( 'progress.js', $action );
}
解决方案
您可以简单地让该脚本立即返回当前进度,并且只需一个 setInterval XHR 循环轮询服务器以获取当前进度。
如果您正在尝试测量上传进度,https: //codepen.io/PerfectIsShit/pen/zogMXP 是使用该progress
事件的一个很好的演示。
xhr.upload.addEventListener("progress", progressHandler, false);
.
如果您想从服务器推送实时更新,您可以查看 SSE/EventSource。 https://www.html5rocks.com/en/tutorials/eventsource/basics/
我不完全确定您正在解决什么问题或您想要实现什么,但我已经概述了下面的所有内容。您似乎误解了“阻塞”和异步的含义。您似乎希望将流式处理和长轮询行为作为 XHR 和 PHP 页面的神奇内置功能,这与异步并不相同。这不是异步和“非阻塞”所指的。这两个术语是指“阻塞”,因为执行 Javascript 语句while(true){}
将“阻塞”执行并锁定您的浏览器,或者XHR.send('url','GET',async=false)
会导致 Javascript 执行流程在该语句处停止,直到 XHR 请求完成(以及冻结浏览器)。
您尝试执行的操作将需要 XHR 流式传输或 ReadableStream 并获取,并且类似于长轮询或分块编码轮询。
通常,当您需要服务器推送时,您会考虑这一点。您可以查看 SSE/EventSource 和 Websockets。另一个用例是大型下载/上传或提交时需要一些处理时间的东西。
我不推荐您采用的方法,因为它依赖于无限期保持 XHR 连接,这违反了一般设计,并且您正在建模的问题类别的解决方案具有更简单的解决方案。如果您必须这样做,您可能需要查看一些库来为您执行此操作。
解决 OP 中提出的问题的最佳方法是决定用什么衡量进展并返回它。您可以拥有一个服务器守护程序来更新您的服务器端 API 将返回的值。对于 OP 中的问题,您可以使用 $_SESSION 并存储时间,然后使用它来计算进度。
至于“阻塞”的问题,UI 模式通常是有某种“正在加载”的指示符,它会一直持续到超时。您将发出一个等待的异步请求,然后您的 UI 可以在请求解析时继续更新,并且依赖于数据的代码可以继续执行。或者它拒绝,或者花费太长时间,你处理错误。
Promise 就像将回调包装在对象中一样。调用一个异步函数,并立即返回一个 Promise,然后可以使用这个 Promise 来使用.then
and来挂钩异步请求的完成.reject
。
例如:
var aPromise = myAsyncFetchRequest();
function callbackHandler(fetchReturnValue){ /* take my data and do something with it*/ }
var promise2 = aPromise.then(callbackHandler);
// do stuff ... stuff that can continue to execute while the Promise resolves
// stuff that could take aPromise and pass it to other functions or add on more handlers
promise2.then(anotherCallbackHandler);
我又看了一遍,您的 PHP 代码看起来就是问题所在。该代码看起来不起作用。对 index.php?start 的第一个请求不会终止。
我强烈建议避免使用脚本注入进行数据传输,除非您有特殊需要。
我已经对您的代码进行了快速模拟演示。它似乎工作正常。我会检查您的网络流量以确保正在下载脚本。您可以在进度调用中放置“调试器”断点,以尝试查看它到达的位置并遍历执行流程。
虽然我强烈建议不要使用单个文件来处理进度,并且可能尝试在 Javascript 和 HTML 和 PHP 代码之间创建更好的分离。
我所做的是模拟您的服务器端代码,并将脚本插入的 src 替换为从数组内容的伪造文件中提取的文本。“逻辑”似乎是合理的,如果有点不稳定的话。
<script>
var files={};
function file_put_contents(file, x){ files[file]=x }
function progress_php($do,$complete=false){
$action = `${$do}; \n`;
if( $complete ) $action = "clearInterval(prgs);\n" + $action;
file_put_contents( 'progress.js', $action );
}
var ii=0;
files['index.php?start']='progress(0);start()';
function start(){setInterval(()=> (ii++>=100?progress_php(`progress(${ii})`,true):progress_php(`progress(${ii})`)),100);
}
// dirty mock server side code
</script>
<script>
function doAsync( task ){
document.querySelector('#status').textContent = 'Running...';
setTimeout( () => { scload('index.php?start') }, 100 );
return new Promise( (resolve,reject) => {
resolve( prgs = setInterval( () => { scload('progress.js') }, 1000 ) );
} );
}
</script>
<script>
function scload(src){
console.log(files[src]);
if(document.querySelector('#scriptloader'))
document.querySelector('#scriptloader').remove();
let s = document.createElement('script');
s.id = 'scriptloader';
// s.setAttribute('async',true);
s.text = files[src];
document.body.appendChild(s);
}
</script>
<script>
function progress(x){
console.log(x);
document.querySelector('.progress').classList.toggle('hide',true);
if( x >= 100 ){
x = 100;
document.querySelector('#status').textContent = 'Complete.';
}
document.querySelector('progress').value = x;
document.querySelector('#progress span').style.width = x+'%';
document.querySelector('.progress').classList.toggle('hide',false);
}
</script>
<style>
body { background: #fff; }
progress { width: 500px; height: 40px; display: block; }
.progress.hide { visibility: hidden; }
#progress { position: relative; width: 500px; height: 20px; background: #aaa;
border-radius: 10px; border: 1px inset #000; box-sizing: border-box; overflow: hidden; }
#progress > span { position: absolute; height: 100%; background: #88f;
box-sizing: border-box; border-radius: 10px 0 0 10px; }
</style>
</head>
<body>
<div class="progress">
<progress class="" value="" max="100"></progress>
<div id="progress"><span></span></div><br>
</div>
<button onclick="doAsync()">Start</button>
<span id="status"></span>
</body>
</html>
推荐阅读
- splunk - 从 ossec 升级到 wazuh - “本地/独立”模式?
- spring-data-jpa - 以通用方式扩展 JpaRepository,同时可以访问它的方法(即没有新的基础项目存储库或单存储库扩展接口)
- sql-server - 窗口函数没有给出运行总数
- bad-gateway - 我想通过拦截器重现 502 bad gateway 错误。任何步骤如何重现它?
- amazon-web-services - sagemaker 'metric_definitions' 没有给出任何结果
- assembly - 使用 Dosbox 比较汇编语言 [MASM] 中的浮点数
- ruby-on-rails - 将数据从一个(可怕的)数据库增量复制到 Rails 中的另一个(更好的)数据库
- pytorch - 使用 PyTorch 模型执行推理时子进程挂起
- python - 如何删除 Wordcloud 中的标点符号?
- spring - Spring 声明式缓存测试仅在测试直接调用 @Cacheable 方法时才有效