我已阅读this question,但它并未完全回答我的问题。
不幸的是,自从我上次查看AJAX以来,看起来XHR对象中的内容发生了变化,因此在完成填充之前不再可以直接访问responseText
。
我必须编写一个使用AJAX的页面(最好是jQuery,但我愿意接受建议)通过HTTP从我无法控制的服务器检索CSV数据。响应数据可能非常大;一兆字节的文字并不少见。
服务器是流友好的。是否有任何方法可以直接从JavaScript返回数据流?
我可以选择编写一些生活在中间的PHP代码并使用某种“Comet”技术(长轮询,EventSource等),但我希望尽可能避免这种情况。
如果它是相关的,假设这个问题用户拥有最新版本的Firefox / Chrome / Opera并且旧的浏览器兼容性不是问题。
答案 0 :(得分:58)
输出文字或HTML 时,这非常简单。以下是一个例子。
(如果尝试输出 JSON ,你会遇到问题,但我会进一步解决这个问题。)
header('Content-type: text/html; charset=utf-8');
function output($val)
{
echo $val;
flush();
ob_flush();
usleep(500000);
}
output('Begin... (counting to 10)');
for( $i = 0 ; $i < 10 ; $i++ )
{
output($i+1);
}
output('End...');
<!DOCTYPE>
<html>
<head>
<title>Flushed ajax test</title>
<meta charset="UTF-8" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var last_response_len = false;
$.ajax('./flushed-ajax.php', {
xhrFields: {
onprogress: function(e)
{
var this_response, response = e.currentTarget.response;
if(last_response_len === false)
{
this_response = response;
last_response_len = response.length;
}
else
{
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
console.log(this_response);
}
}
})
.done(function(data)
{
console.log('Complete response = ' + data);
})
.fail(function(data)
{
console.log('Error: ', data);
});
console.log('Request Sent');
</script>
</body>
</html>
实际上不可能以递增方式加载单个JSON对象(在它完全加载之前),因为在您拥有完整对象之前,语法将始终无效。
但是如果你的响应有一个接一个多个 JSON对象,那么就可以一次加载一个,因为它们会从管道中下载。
所以我在上面调整了我的代码...
将PHP FILE第4行从echo $val;
更改为echo '{"name":"'.$val.'"};'
。这会输出一系列JSON对象。
将HTML FILE行24从console.log(this_response);
更改为
this_response = JSON.parse(this_response);
console.log(this_response.name);
请注意,此基本代码假定进入浏览器的每个“块”都是有效的JSON对象。情况并非总是如此,因为您无法预测数据包将如何到达 - 您可能需要根据分号分割字符串(或者提出另一个分隔符)。
application/json
NOT 将标题更改为application/json
- 我这样做了,它让我谷歌搜索了3天。当响应类型为application/json
时,浏览器会等待响应完成,如完全完成。然后解析完整响应以检查它是否是实际的JSON。但是我们的FULL响应是{...};{...};{...};
,它不是有效的JSON。 jqXHR.done
方法假定存在错误,因为无法将完整响应解析为JSON。
如评论中所述,您可以使用以下方法在客户端禁用此检查:
$.ajax(..., {dataType: "text"})
希望有些人觉得这很有用。
答案 1 :(得分:32)
使用XMLHttpRequest.js
https://github.com/ilinsky/xmlhttprequest
http://code.google.com/p/xmlhttprequest
使用PHP进行长轮询:
output.php:
<?php
header('Content-type: application/octet-stream');
// 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;
}
// Disable apache output buffering/compression
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
// Count to 20, outputting each second
for ($i = 0;$i < 20; $i++) {
echo $i.str_repeat(' ', 2048).PHP_EOL;
flush();
sleep(1);
}
run.php:
<script src="http://code.jquery.com/jquery-1.6.4.js"></script>
<script src="https://raw.github.com/ilinsky/xmlhttprequest/master/XMLHttpRequest.js"></script>
<script>
$(function() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/longpoll/', true);
xhr.send(null);
var timer;
timer = window.setInterval(function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
window.clearTimeout(timer);
$('body').append('done <br />');
}
$('body').append('state: ' + xhr.readyState + '<br />');
console.log(xhr.responseText);
$('body').append('data: ' + xhr.responseText + '<br />');
}, 1000);
});
</script>
这应输出:
state: 3
data: 0
state: 3
data: 0 1
state: 3
data: 0 1 2
state: 3
data: 0 1 2 3
state: 3
data: 0 1 2 3 4
...
...
...
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
done
state: 4
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
对于IE,您需要查看XDomainRequest
http://msdn.microsoft.com/en-us/library/cc288060(VS.85).aspx
答案 2 :(得分:19)
你会想要直接使用javascript。原因是你想要不断轮询而不是等待回调。你不需要jQuery,这很简单。他们有一些nice source code for this on the Ajax Patterns website。
基本上,您只是想跟踪您在响应中的最后位置,并定期轮询该位置之外的更多文本。您的情况不同,您可以订阅完整的活动并停止投票。
答案 3 :(得分:15)
既然你说你的服务器是流友好的(异步)并且正在寻找一个jquery解决方案,你有没有检查过jQuery Stream Plugin?
它非常易于使用,让您不必担心任何事情。它也有pretty good documentation。
答案 4 :(得分:0)
这是使用JQuery(根据OP的要求)实现此目的的直接方法:
首先,通过运行https://gist.github.com/chrishow/3023092中的以下代码(附加在此响应的底部),扩展ajax对象以支持onreadystatechange。然后使用onreadystatechange函数调用ajax,该函数将检查xhr.responseText以获取新文本。
如果你想变得更加漂亮,你可以在每次阅读时清除responseText数据,例如描述here)。
例如,请参阅https://jsfiddle.net/g1jmwcmw/1/,它将从https://code.jquery.com/jquery-1.5.js下载响应并使用下面的代码将其输出到控制台窗口内的块中(您可以将其复制到html页面然后在浏览器中打开):
<!-- jquery >= 1.5. maybe earlier too but not sure -->
<script src=https://code.jquery.com/jquery-1.5.min.js></script>
<script>
/* One-time setup (run once before other code)
* adds onreadystatechange to $.ajax options
* from https://gist.github.com/chrishow/3023092)
* success etc will still fire if provided
*/
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
if ( options.onreadystatechange ) {
var xhrFactory = options.xhr;
options.xhr = function() {
var xhr = xhrFactory.apply( this, arguments );
function handler() {
options.onreadystatechange( xhr, jqXHR );
}
if ( xhr.addEventListener ) {
xhr.addEventListener( "readystatechange", handler, false );
} else {
setTimeout( function() {
var internal = xhr.onreadystatechange;
if ( internal ) {
xhr.onreadystatechange = function() {
handler();
internal.apply( this, arguments );
};
}
}, 0 );
}
return xhr;
};
}
});
// ----- myReadyStateChange(): this will do my incremental processing -----
var last_start = 0; // using global var for over-simplified example
function myReadyStateChange(xhr /*, jqxhr */) {
if(xhr.readyState >= 3 && xhr.responseText.length > last_start) {
var chunk = xhr.responseText.slice(last_start);
alert('Got chunk: ' + chunk);
console.log('Got chunk: ', chunk);
last_start += chunk.length;
}
}
// ----- call my url and process response incrementally -----
last_start = 0;
$.ajax({
url: "https://code.jquery.com/jquery-1.5.js", // whatever your target url is goes here
onreadystatechange: myReadyStateChange
});
</script>
答案 5 :(得分:0)
我必须为网格提供一个带有较大JSON负载的网格,该负载一直运行到允许的最大大小限制。我当时使用的是MVC和jquery,所以我修改了上面的AlexMorley-Finch的解决方案。
服务器代码来自"Streaming data using Web API"。还有https://github.com/DblV/StreamingWebApi。
public class StreamingController : ApiController
{
[HttpGet]
[ActionName("GetGridDataStream")]
public HttpResponseMessage GetGridDataStream(string id)
{
var response = Request.CreateResponse();
DynamicData newData = new DynamicData();
var res = newData.GetDataRows(id);
response.Content = new PushStreamContent((stream, content, context) =>
{
foreach (var record in res)
{
var serializer = new JsonSerializer();
using (var writer = new StreamWriter(stream))
{
serializer.Serialize(writer, record);
stream.Flush();
}
// Thread.Sleep(100);
}
stream.Close();
});
return response;
}
}
这创建了{json对象} {json对象} {json对象}的流,该流需要用逗号分隔并包围[]才能成功解析为json。
为客户代码提供了缺少的字符,因此:
var jsonData = {};
$.ajax("api/Streaming/GetGridDataStream/" + viewName, {
xhrFields: {
onprogress: function (e) {
// console.log(this_response);
}
}
}, { dataType: "text" }) //<== this is important for JSON data
.done(function (data) {
data = "[" + data.replace(/\}\{/gi, "},{") + "]";
jsonData["DataList"] = JSON.parse(data);
//more code follows to create grid
})
.fail(function (data) {
console.log('Error: ', data);
});
我希望这对使用.Net MVC和jQuery的人有所帮助。