JQuery читать поток AJAX постепенно? - PullRequest
68 голосов
/ 12 октября 2011

Я прочитал этот вопрос , но он не совсем отвечает на мой вопрос.К сожалению, похоже, что в объекте XHR все изменилось с тех пор, как я в последний раз смотрел на AJAX, поэтому больше невозможно напрямую получить доступ к responseText до его заполнения.

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

Сервер дружествен к потоку.Есть ли еще способ получить доступ к потоку данных, когда они возвращаются, непосредственно из JavaScript?

У меня есть возможность написать некоторый код PHP, который живет посередине и использует что-то вроде "Комета "tech (long-polling, EventSource и т. Д.), Но я бы предпочел этого избежать, если это возможно.

В случае, если это уместно, предположим, что для этого вопроса пользователи имеют последнюю версию Firefox/ Chrome / Opera и старый браузер совместимость не проблема.

Ответы [ 5 ]

61 голосов
/ 23 сентября 2013

Это довольно просто при выводе текста или HTML . Ниже приведен пример.

(Однако при попытке вывести JSON вы столкнетесь с проблемами, о которых я расскажу далее.)

PHP FILE

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. Изменение строки 4 ФАЙЛА PHP с 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 - я сделал это, и я получил Google в течение 3 дней. Когда тип ответа application/json, браузер ожидает, пока ответ не будет завершен, как в случае полного завершения. Полный ответ затем анализируется, чтобы проверить, является ли он действительным JSON. Однако наш полный ответ {...};{...};{...};, который НЕ является действительным JSON. В методе jqXHR.done предполагается, что произошла ошибка, поскольку полный ответ не может быть проанализирован как JSON.

Как уже упоминалось в комментариях, вы можете отключить эту проверку на стороне клиента с помощью:

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

Надеюсь, что некоторые люди находят это полезным.

32 голосов
/ 02 ноября 2011

Использовать XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

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

  • Обеспечивает ненавязчивую совместимую со стандартами (W3C) кросс-браузерную реализацию XMLHttpRequest 1.0object
  • Исправляет ВСЕ браузеры, обнаруженные в их собственных реализациях объекта 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

19 голосов
/ 12 октября 2011

Вы захотите использовать для этого прямой JavaScript.Причина в том, что вы захотите постоянно опрашивать, а не ждать срабатывания обратных вызовов.Вам не нужен jQuery для этого, это довольно просто.У них есть хороший исходный код для этого на веб-сайте Ajax Patterns .

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

15 голосов
/ 01 ноября 2011

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

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

0 голосов
/ 13 ноября 2017

Вот простой способ добиться этого с помощью 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>
...