jQuery поступово читає потік AJAX?


78

Я прочитав це запитання, але воно точно не відповідає на моє запитання. На жаль, схоже, що щось змінилося в об’єкті XHR з того часу, як я востаннє дивився на AJAX, тому більше неможливо безпосередньо отримати доступ responseTextдо того, як його буде заповнено.

Мені потрібно написати сторінку, яка використовує AJAX (бажано jQuery, але я відкрита для пропозицій) для отримання даних CSV через HTTP із сервера, над яким я не маю контролю. Дані відповіді можуть бути досить великими; мегабайт тексту - не рідкість.

Сервер зручний для потоку. Чи існує ще якийсь спосіб отримати доступ до потоку даних у міру їх повернення безпосередньо з JavaScript?

У мене є можливість написати якийсь PHP-код, який живе посередині і використовує якусь техніку "Комета" (довгі опитування, EventSource тощо), але я волів би уникати цього, якщо це можливо.

Якщо це доречно, припустимо, що для цього питання користувачі мають останню версію Firefox / Chrome / Opera, а сумісність із старими браузерами не є проблемою.


Я знаю, що на це відповіли, я робив щось подібне раніше, подивись, вирви
MrJD

Відповіді:


21

Ви хочете використовувати для цього просто javascript вгору. Причина в тому, що ви хочете постійно проводити опитування і не чекати, поки зворотні дзвінки спрацюють. Для цього вам не потрібен jQuery, це досить просто. У них є хороший вихідний код для цього на веб-сайті Ajax Patterns .

По суті, ви просто захочете відслідковувати свою останню позицію у відповіді та періодично опитувати більше тексту за цим місцем. Різниця у вашому випадку полягає в тому, що ви можете підписатися на повну подію та зупинити опитування.


3
Чи можете ви вказати мені на робочий приклад? У наведеному вами посиланні сказано, що "Властивість responseText XMLHttpRequest завжди містить вміст, який було видалено із сервера, навіть коли з'єднання все ще відкрите." .. і, з того, що я читав, у нових браузерах це вже не так.
Джош

Хіба це не просто в IE? Я думав, readyState 3 містить його в інших браузерах.
scottheckel

1
В першу чергу я йшов за ПРИМІТКОЮ в цьому плагіні jquery: plugins.jquery.com/project/ajax-http-stream 'ПРИМІТКА: Я прийшов до моєї уваги, що це більше не працює з Firefox 3.0.11 (працює в 3.0. 8 на Linux), IE8 або остання версія Chrome. Очевидно, тенденція полягає у забороні доступу до xmlhttprequest.responseText до завершення запиту (дурне imo). Вибачте, я нічого не можу зробити, щоб це виправити '
Джош,

Виявляється, це насправді працює з простою версією javascript, після простої спроби (принаймні з браузерами, які поводяться належним чином). Все ще сподіваючись знайти версію jquery, щоб вона працювала коректно у всіх браузерах, але наразі це найкраща відповідь.
Джош

мертві посилання мене
засмучують

77

Це досить просто при виведенні тексту або 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. Зміна рядка 24 ФАЙЛА HTML з console.log(this_response);на

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

    Зверніть увагу, що цей елементарний код передбачає, що кожен "шматок", що надходить у браузер, є дійсним об'єктом JSON. Це буде не завжди так, оскільки ви не можете передбачити, як надходитимуть пакети - можливо, вам доведеться розділити рядок на основі крапки з комою (або запропонувати інший символ-роздільник).

Не використовуйте application/json

Do NOT Для зміни ваших заголовків application/json- я зробив це , і це було мені погуглити в протягом 3 днів. Коли тип відповіді є application/json, браузер чекає, поки відповідь не завершиться, як і в повній мірі. Потім повна відповідь аналізується, щоб перевірити, чи це фактично JSON. Однак наша ПОВНА відповідь - {...};{...};{...};це НЕ дійсний JSON. jqXHR.doneМетод передбачає , що була помилка, тому що повну відповідь не може бути розібраний як JSON.

Як зазначалося в коментарях, ви можете вимкнути цю перевірку на стороні клієнта, використовуючи:

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

Сподіваюся, деякі люди вважають це корисним.


1
Вау дякую, сер, це було саме те, що я шукав! Дуже хороший приклад того, як використовувати цю техніку з JSON.
Аарон

3
Велике спасибі, це зайняло у мене 1 хвилину для успішної реалізації. Чудові речі.
Pål Thingbø

1
Викликайте $ .ajax з {dataType: "text"}, це заважатиме розумному відгадуванню (див. Api.jquery.com/jquery.ajax dataType)
Крістоф Квінтард

1
Так, ви можете читати JSON поступово, використовуючи потоковий синтаксичний аналізатор JSON, такий як гобой ( oboejs.com ). Вам не потрібно змінювати свою відповідь JSON, щоб мати кілька об’єктів JSON, і, мабуть, краще не робити це з точки зору дизайну
mwag,

1
Примітка щодо PHP: Як правило, поганою практикою є створення JSON вручну на кінці PHP шляхом конкатенації рядків (наприклад, echo '{"name":"'.$val.'"};'). Може бути якийсь кращий код echo json_encode(["name"=>$val]).";";.
Лаеф

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/en-us/library/cc288060(VS.85).aspx


Здається, це не підтримує readystate 3, навіть у Chrome :(
Джош,

1
@Josh, так, це так. Але існують різні примхи з тривалими опитуваннями. Вам потрібно надіслати 2 Кб даних, перш ніж стан читання зміниться, а також встановити тип вмісту application/octet-stream. Див. Мій оновлений пост для прикладу PHP.
Петах,

Я побачу, що я можу з цим зробити. Здається неминучим, що мені знадобиться PHP посередині, оскільки я не можу контролювати тип вмісту оригінальної відповіді. Але я також дуже хотів би мати можливість підтримувати IE6 / 7 (на жаль) ...
Джош

1
@xorinzor pastebin.com/3Dbt2mhQ, однак, залежно від ваших потреб, можливо, вам доведеться застосувати власний протокол. Таке зчитування всіх даних до ;.
Петах

3
@Bakalash, оскільки деякі браузери не дозволяють потокове передавання, поки не буде надіслано 2 кб вихідних даних.
Петах

16

Оскільки ви говорите, що ваш сервер зручний для потоку (асинхронний) і шукав рішення jquery, ви перевіряли плагін jQuery Stream ?

Він дуже простий у використанні і дозволяє вам не дуже турбуватися про щось. Він також має досить хорошу документацію .


Я, звичайно, можу поглянути на це. У короткому перегляді сторінки API я не бачу способу надсилати на сервер інформацію про HTTP POST та базову автентифікацію, але я впевнений, що вона десь там має бути. Також, можливо, "потік дружній" був неправильним вибором терміна. Я не маю на увазі асинхронний або двонаправлений. Я мав на увазі, що він надсилає назад велику кількість даних у часі, у потоці, як гігантська відповідь HTTP. Крім того, тим часом я знайшов рішення, яке не відповідає вимогам jQuery, яке має бути "достатньо хорошим" для моїх початкових цілей.
Джош

добре для публікації http та базової автентифікації, ви все одно використовували б прямий jquery.
g19fanatic

І як мені інтегрувати "прямий jquery в будь-якому випадку" з плагіном jquery stream? Документи з цього приводу незрозумілі. Отримали приклад?
Джош

5
+1 Зараз це перетворилося на портал, і це виглядає по-справжньому чудово, охоплюючи WebSockets та все інше. github.com/flowersinthesand/portal
marsbard

1
Портал @marsbard досяг кінця життя і більше не підтримується! Використовуйте Vibe .
Donghwan Kim

0

Ось прямий спосіб досягти цього за допомогою 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>

OP тут. Питання було задане 6 років тому. Це щось, що могло б спрацювати у 2011/2012? Я більше не працюю над цим проектом, тому не зможу перевірити вашу відповідь.
Джош

так, він чудово працює з jquery 1.5 ( січень 2011, code.jquery.com/jquery-1.5.min.js ). Як приклад, ви можете просто вирізати / вставити код вище.
mwag

Мені просто доведеться тобі довіряти. Я запустив ваш точний код у декількох браузерах, і вся відповідь була одним шматком, тож це насправді нічого не довело. Не встигайте возитися з цим далі.
Джош

Ви мали б змогу це побачити. Я зберег вищезазначене, дослівно, у файл test.html і відкрив його в Chrome, і вікно консолі відображало відповідь, отриману двома шматками.
mwag

0

Мені довелося забезпечити мережу великим корисним навантаженням 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.