jQuery 以增量方式读取 AJAX 流?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7740646/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 00:13:40  来源:igfitidea点击:

jQuery read AJAX stream incrementally?

jqueryajaxdom-eventslong-pollinghttp-streaming

提问by Josh

I have read this questionbut it doesn't exactly answer my question. Unfortunately, it looks like things have changed in in the XHR object since I last looked at AJAX, so it is no longer possible to directly access responseTextbefore it is finished being populated.

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

I have to write a page that uses AJAX (preferably jQuery, but I am open to suggestions) to retrieve CSV data via HTTP from a server I have no control over. The response data could be quite large; a megabyte of text is not uncommon.

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

The server is stream-friendly. Is there still any way to get access to a stream of data as it is being returned, directly from JavaScript?

服务器是流友好的。是否还有任何方法可以直接从 JavaScript 访问正在返回的数据流?

I do have the option of writing some PHP code that lives in the middle and uses some sort of "Comet" tech (long-polling, EventSource, etc), but I would prefer to avoid that if possible.

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

In case it is relevant, assume for this question that users have the latest version of Firefox/Chrome/Opera and old browser compatibility is not an issue.

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

采纳答案by scottheckel

You're going to want to use straight up javascript for this. The reason is that you're going to want to continuously poll and not wait for the callbacks to fire. You don't need jQuery for this, it's pretty simple. They have some nice source code for this on the Ajax Patterns website.

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

Essentially, you'll just want to keep track of your last position in the response and periodically poll for more text past that location. The difference in your case is that you can subscribe to the complete event and stop your polling.

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

回答by AlexMorley-Finch

This is quite straightforward when outputting text or HTML. Below is an example.

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

(You'll run into issues if trying to output JSONhowever, which I'll tackle further down.)

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

PHP FILE

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 FILE

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>

What if I need to do this with JSON?

如果我需要使用 JSON 执行此操作怎么办?

It's not actually possible to load a single JSON object incrementally (before it's fully loaded) because until you have the complete object, the syntax will always be invalid.

实际上不可能增量加载单个 JSON 对象(在它完全加载之前),因为在您拥有完整的对象之前,语法将始终无效。

But if your response has multipleJSON objects, one after another, then it's possible to load one at a time, as they come down the pipe.

但是,如果您的响应具有多个JSON 对象,一个接一个,则可以一次加载一个,因为它们会通过管道传输。

So I tweaked my code above by...

所以我调整了我上面的代码...

  1. Changing PHP FILE line 4 from echo $val;to echo '{"name":"'.$val.'"};'. This outputs a series of JSON objects.

  2. Changing HTML FILE line 24 from console.log(this_response);to

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

    Note that this rudimentary code assumes that each "chunk" coming to the browser is a valid JSON object. This will not always be the case because you cannot predict how packets will arrive - you may need to split the string based on semi-colons (or come up with another separator character).

  1. 从修改PHP文件中的行4echo $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 对象。情况并非总是如此,因为您无法预测数据包将如何到达 - 您可能需要根据分号拆分字符串(或提出另一个分隔符)。

Don't use application/json

不要使用 application/json

Do NOTFor change your headers to application/json- I did this and it had me Googling for 3 days. When the response type is application/json, the browser waits until the response is complete, as in fully complete. The full response is then parsed to check if it is infact JSON. However our FULL response is {...};{...};{...};which is NOT valid JSON. The jqXHR.donemethod assumes there was an error, because the complete response cannot be parsed as JSON.

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

As mentioned in the comments, you can disable this check on the client side by using:

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

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

Hope some people find this useful.

希望有些人觉得这很有用。

回答by Petah

Use XMLHttpRequest.js

使用 XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

https://github.com/ilinsky/xmlhttprequest

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

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

  • Delivers unobtrusive standard-compliant (W3C) cross-browser implementation of the XMLHttpRequest 1.0 object
  • Fixes ALL browsers quirks observed in their native XMLHttpRequest object implementations
  • Enables transparent logging of XMLHttpRequest object activity
  • 提供不引人注目的符合标准 (W3C) 的 XMLHttpRequest 1.0 对象的跨浏览器实现
  • 修复了在其原生 XMLHttpRequest 对象实现中观察到的所有浏览器怪癖
  • 启用 XMLHttpRequest 对象活动的透明日志记录

To use long polling with PHP:

在 PHP 中使用长轮询:

output.php:

输出.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:

运行.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>

This should output:

这应该输出:

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 

For IE you need to look into XDomainRequest

对于 IE,您需要查看 XDomainRequest

http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx

http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx

http://msdn.microsoft.com/en-us/library/cc288060(VS.85).aspx

http://msdn.microsoft.com/en-us/library/cc288060(VS.85).aspx

回答by g19fanatic

Since you say your server is stream friendly (asynchronous) and was looking for a jquery solution, have you checked out the jQuery Stream Plugin?

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

It is really easy to use and allows you to not really worry about much of anything. It has pretty gooddocumentationas well.

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

回答by mwag

Here is a straightforward way to achieve this using JQuery (as requested by the OP):

这是使用 JQuery 实现此目的的直接方法(根据 OP 的要求):

First, extend the ajax object to support onreadystatechange by running the below code from https://gist.github.com/chrishow/3023092(appended at the bottom of this response). Then just call ajax using an onreadystatechange function that will check xhr.responseText for new text.

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

If you wanted to get even fancier, you could clear the responseText data each time you read it, such as described here).

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

For example, see https://jsfiddle.net/g1jmwcmw/1/, which will download the response from https://code.jquery.com/jquery-1.5.jsand output it in chunks inside your console window, using the code below (which you can just copy into an html page and then open in your browser):

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

回答by davaus

I had to supply a grid with a large JSON payload that kept running into the maximum allowed size limit. I was using MVC and jquery, and so I adapted the solution of AlexMorley-Finch above.

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

The server code was from "Streaming data using Web API". Also https://github.com/DblV/StreamingWebApi.

服务器代码来自 “使用 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;
    }
}

This created a stream of {json object}{json object}{json object} that needed delimiting commas and surrounding [ ] to be parsed as json successfully.

这创建了一个 {json object}{json object}{json object} 流,需要分隔逗号和周围的 [] 才能成功解析为 json。

The client code was supplied the missing characters thus:

客户端代码提供了缺少的字符,因此:

 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);
    });

I hope this helps someone using .Net MVC and jQuery.

我希望这对使用 .Net MVC 和 jQuery 的人有所帮助。