jQuery逐步读取AJAX流?


78

我已经阅读了这个问题,但是并不能完全回答我的问题。不幸的是,自从我上次查看AJAX以来,XHR对象中的一切似乎已经发生了变化,因此,responseText在完成填充之前,不再可以直接访问。

我必须编写一个页面,该页面使用AJAX(最好是jQuery,但我愿意接受建议)从无法控制的服务器通过HTTP检索CSV数据。响应数据可能会很大;一兆字节的文本并不罕见。

服务器是流友好的。在直接从JavaScript返回数据流时,还有什么方法可以访问它?

我确实可以选择编写一些PHP代码,这些代码位于中间并且使用某种“彗星”技术(长轮询,EventSource等),但是如果可能的话,我希望避免这样做。

如果相关,请假设用户具有Firefox / Chrome / Opera的最新版本,并且与旧浏览器的兼容性不是问题。


我知道已经回答了这个问题,我之前做过类似的事情,看看,如果需要的话,请把它撕掉jsfiddle.net/JmZCE/1
MrJD 2011年

Answers:


21

您将要为此直接使用JavaScript。原因是您将要连续轮询而不是等待回调触发。您不需要jQuery,这非常简单。他们在Ajax Patterns网站上有一些不错的源代码

本质上,您只想跟踪响应中的最后一个位置,并定期轮询该位置之后的更多文本。您的情况不同之处在于,您可以订阅complete事件并停止轮询。


3
您能指出一个可行的例子吗?您提供的链接说:“即使连接仍处于打开状态,XMLHttpRequest的responseText属性始终包含从服务器中清除的内容。” ..而且,根据我的阅读,在新的浏览器中已不再是这种情况。
乔什(Josh)

这不只是在IE中吗?我以为readyState 3包含在其他浏览器中。
scottheckel 2011年

1
最初,我正在使用此jquery插件中 的NOTE :plugins.jquery.com/project/ajax-http-stream '注意:引起我注意的是,此版本自Firefox 3.0.11起不再起作用(在3.0中有效。在Linux上为8),IE8或最新版本的Chrome。显然趋势是在请求完成之前(愚蠢的imo)禁止访问xmlhttprequest.responseText。抱歉,我无能为力”
乔什(Josh)

事实证明,仅在尝试了JavaScript(至少对于行为正常的浏览器)之后,它实际上就可以使用纯JavaScript。仍然希望找到一个jquery版本,以便它在所有浏览器中都能正常运行,但是到目前为止,这毕竟是最好的答案。
乔什(Josh)

死链接使我难过
captncraig 2015年

76

输出文本或HTML时,这非常简单。下面是一个例子。

(但是,如果尝试输出JSON,则会遇到问题,我将进一步解决。)

PHP文件

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...');

HTML文件

<!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对象(在完全加载之前),因为在拥有完整的对象之前,语法将始终无效。

但是,如果您的响应包含一个接一个的多个JSON对象,则可以一次加载一个,因为它们来自管道。

所以我通过以下方式调整了我的代码...

  1. 将PHP FILE第4行从更改echo $val;echo '{"name":"'.$val.'"};'。这将输出一系列JSON对象。

  2. 将HTML FILE第24行从更改console.log(this_response);

    this_response = JSON.parse(this_response);
    console.log(this_response.name);
    

    请注意,此基本代码假定进入浏览器的每个“块”都是有效的JSON对象。并非总是如此,因为您无法预测数据包将如何到达-您可能需要根据分号对字符串进行分割(或提供另一个分隔符)。

不要使用 application/json

难道不是为了改变你的头来application/json-我做了这一点,它让我谷歌搜索3天。当响应类型application/json为时,浏览器将等待,直到响应完成为止,就像完全完成一样。然后解析完整的响应以检查它是否为JSON。但是,我们的FULL响应是{...};{...};{...};无效的JSON。该jqXHR.done方法假定存在错误,因为无法将完整的响应解析为JSON。

如评论中所述,您可以使用以下命令在客户端禁用此检查:

$.ajax(..., {dataType: "text"})

希望有人觉得这有用。


1
哇,谢谢您,先生,这正是我想要的!关于如何将此技术与JSON一起使用的很好的例子。
亚伦

3
非常感谢,这花了我1分钟时间成功实施。好东西。
2014年

1
使用{dataType:“ text”}调用$ .ajax,这将阻止智能猜测(请参阅api.jquery.com/jquery.ajax dataType)
Christophe Quintard,2016年

1
是的,您可以使用流JSON解析器(例如oboe(oboejs.com))以增量方式读取JSON 。您无需将JSON响应更改为具有多个JSON对象,最好不要从设计角度进行
更改

1
关于PHP的注释:通常,在PHP端通过串联字符串(例如echo '{"name":"'.$val.'"};')来手动创建JSON是一种不好的做法。一些更好的代码可能是echo json_encode(["name"=>$val]).";";
Laef

34

使用XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

http://code.google.com/p/xmlhttprequest

  • 提供XMLHttpRequest 1.0对象的兼容标准(W3C)跨浏览器实现
  • 修复了在本机XMLHttpRequest对象实现中观察到的所有浏览器怪癖
  • 启用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://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx

http://msdn.microsoft.com/zh-CN/library/cc288060(VS.85).aspx


这似乎不支持readystate 3,甚至在Chrome中也不支持:(
Josh

1
@Josh,是的。但是,有很多具有长轮询功能的怪癖。您需要发送2Kb的数据,然后读取状态才会更改,并将内容类型设置为application/octet-stream。请参阅我更新的帖子中的PHP示例。
Petah

我将看到我能做什么。似乎不可避免地需要在中间添加一些PHP,因为我无法控制原始响应的内容类型。但我也真的很希望能够支持IE6 / 7(不幸的是)...
Josh

1
@xorinzor pastebin.com/3Dbt2mhQ但是,根据您的需要,您可能需要实现自定义协议。这样读取所有数据直到a为止;
Petah 2013年

3
@Bakalash,因为某些浏览器在发送2kb的输出之前不允许流式传输。
佩塔

16

既然您说您的服务器是流友好的(异步的)并且正在寻找jquery解决方案,那么您是否签出了jQuery Stream Plugin

它非常易于使用,使您不必担心任何事情。它也有很好的 文档


我当然可以看一下。快速浏览一下API页面,我看不到将HTTP POST和基本身份验证信息发送到服务器的方法,但是我敢肯定它必须在某个地方。也可能“流友好”是术语的错误选择。我不是说异步或双向。我的意思是,随着时间的流逝,它会像巨大的HTTP响应一样发送回大量数据。同时,同时,我发现了一个非jquery解决方案,对于我的原始目的,该解决方案应该“足够好”。
乔什(Josh)

对于http发布和基本身份验证而言,无论如何,您还是应该使用直接的jquery。
g19fanatic 2011年

以及如何将“直接的jquery”与jquery流插件集成在一起?关于这一点,文档尚不清楚。有一个例子吗?
乔什(Josh)

5
+1现在变成门户了,它看起来真的很棒,涵盖了WebSocket和所有内容。github.com/flowersinthesand/portal
marsbard

1
@marsbard Portal已到达使用寿命,并且不再维护!使用Vibe
Donghwan Kim 2014年

0

这是使用JQuery(按OP的要求)实现此目的的简单方法:

首先,通过运行来自https://gist.github.com/chrishow/3023092的以下代码,扩展ajax对象以支持onreadystatechange(在此响应的底部追加)。然后只需使用onreadystatechange函数调用ajax,该函数将检查xhr.responseText是否有新文本。

如果您想变得更高级,可以在每次阅读时清除responseText数据,例如此处所述)。

例如,请参阅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>

OP在这里。这个问题是6年前提出的。这会在2011/2012年生效吗?我不再从事这个项目,所以我将无法测试您的答案。
乔什(Josh)

是的,它与jquery 1.5兼容(2011年1月,code.jquery.com/ jquery- 1.5.min.js)。例如,您可以剪切/粘贴上面的代码。
mwag

我只需要相信你。我在多个浏览器中运行了您的确切代码,并且整个响应都在一个“块”中,因此它并没有真正证明任何事情。没有时间进一步弄弄它了。
乔什(Josh)

您应该能够看到它。我将以上内容逐字保存到test.html文件中,并在Chrome中打开了文件,控制台窗口将收到的响应分为两个部分。
mwag

0

我必须提供一个带有大JSON有效负载的网格,该网格一直运行到允许的最大大小限制。我使用的是MVC和jquery,因此我改写了上述AlexMorley-Finch的解决方案。

服务器代码来自 “使用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的人有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.