JQuery читать поток AJAX постепенно?
Я прочитал этот вопрос, но он не совсем отвечает на мой вопрос. К сожалению, похоже, что все изменилось в объекте XHR с тех пор, как я в последний раз смотрел на AJAX, поэтому прямой доступ к нему больше невозможен responseText
до того, как он будет закончен, будет заселен.
Я должен написать страницу, которая использует AJAX (желательно jQuery, но я открыт для предложений) для получения данных CSV через HTTP с сервера, который я не контролирую. Данные ответа могут быть довольно большими; мегабайт текста не редкость.
Сервер дружественный к потоку. Есть ли еще способ получить доступ к потоку данных, когда они возвращаются, непосредственно из JavaScript?
У меня есть возможность написать некоторый код PHP, который живет посередине и использует какую-то технологию "Comet" (long-polling, EventSource и т. Д.), Но я бы предпочел избежать этого, если это возможно.
В случае, если это уместно, предположим, что пользователи имеют последнюю версию Firefox/Chrome/Opera и совместимость со старым браузером не является проблемой.
5 ответов
Вы будете хотеть использовать прямой JavaScript для этого. Причина в том, что вы захотите постоянно опрашивать, а не ждать срабатывания обратных вызовов. Вам не нужен jQuery для этого, это довольно просто. У них есть хороший исходный код для этого на веб-сайте Ajax Patterns.
По сути, вы просто хотите отслеживать свою последнюю позицию в ответе и периодически запрашивать больше текста после этого местоположения. Разница в вашем случае заключается в том, что вы можете подписаться на полное событие и прекратить свой опрос.
Это довольно просто при выводе текста или 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, один за другим, то можно загружать по одному за раз, когда они приходят по конвейеру.
Так что я поправил свой код выше...
Изменение строки PHP FILE 4 из
echo $val;
вecho '{"name":"'.$val.'"};'
, Это выводит серию объектов JSON.Изменение строки 24 ФАЙЛА HTML из
console.log(this_response);
вthis_response = JSON.parse(this_response); console.log(this_response.name);
Обратите внимание, что этот элементарный код предполагает, что каждый "кусок", поступающий в браузер, является допустимым объектом JSON. Это не всегда так, потому что вы не можете предсказать, как будут поступать пакеты - вам может понадобиться разбить строку на точки с запятой (или придумать другой символ-разделитель).
Не использовать application/json
НЕ ЗАМЕНЯЙТЕ свои заголовки на application/json
- Я сделал это, и это заставило меня гуглить 3 дня. Когда тип ответа application/json
браузер ожидает, пока ответ не будет завершен, как в случае полного завершения. Полный ответ затем анализируется, чтобы проверить, является ли он действительным JSON. Однако наш полный ответ {...};{...};{...};
который НЕ является действительным JSON. jqXHR.done
Метод предполагает, что произошла ошибка, потому что полный ответ не может быть проанализирован как JSON.
Как упоминалось в комментариях, вы можете отключить эту проверку на стороне клиента, используя:
$.ajax(..., {dataType: "text"})
Надеюсь, что некоторые люди находят это полезным.
Используйте XMLHttpRequest.js
https://github.com/ilinsky/xmlhttprequest
http://code.google.com/p/xmlhttprequest
- Предоставляет ненавязчивую совместимую со стандартами (W3C) кросс-браузерную реализацию объекта XMLHttpRequest 1.0
- Исправляет ВСЕ браузерные причуды, наблюдаемые в их собственных реализациях объекта 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://msdn.microsoft.com/en-us/library/cc288060(VS.85).aspx
Поскольку вы говорите, что ваш сервер дружественный к потоку (асинхронный) и искал решение для jquery, вы воспользовались плагином jQuery Stream?
Он действительно прост в использовании и позволяет вам не беспокоиться ни о чем. Он также имеет довольно хорошую документацию.
Мне пришлось предоставить сетку с большой полезной нагрузкой JSON, которая продолжала работать с максимально допустимым размером. Я использовал MVC и jquery, поэтому адаптировал решение AlexMorley-Finch, описанное выше.
Код сервера был взят из "Потоковой передачи данных с использованием веб-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 object}{json object}{json object}, который нуждался в разделительных запятых и окружении [ ] для успешного анализа как 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.
Вот простой способ добиться этого с помощью JQuery (как запрашивается OP):
Во-первых, расширьте объект ajax для поддержки onreadystatechange, запустив приведенный ниже код с https://gist.github.com/chrishow/3023092 (добавлено в нижней части этого ответа). Затем просто вызовите ajax с помощью функции onreadystatechange, которая проверит 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>