jQuery czytać strumień AJAX stopniowo?

Przeczytałem to pytanie ale nie do końca odpowiada na moje pytanie. Niestety, wygląda na to, że coś się zmieniło w obiekcie XHR od czasu, gdy ostatnio patrzyłem na AJAX, więc nie jest już Możliwy bezpośredni dostęp do responseText, zanim zostanie wypełniony.

Muszę napisać stronę, która używa Ajaksa (najlepiej jQuery, ale jestem otwarty na sugestie), aby pobrać dane CSV przez HTTP z serwera, nad którym nie mam kontroli. DANE odpowiedzi mogą być dość duże; megabajt tekstu nie jest rzadkością.

Serwer jest przyjazny dla strumienia. Czy jest jeszcze jakiś sposób, aby uzyskać dostęp do strumienia danych, gdy jest zwracany, bezpośrednio z JavaScript?

Mam możliwość napisania jakiegoś kodu PHP, który mieszka w środku i używa jakiejś "komety" technologii( long-polling, EventSource itp.), ale wolałbym tego uniknąć, jeśli to możliwe.

jeśli jest to istotne, załóżmy dla tego pytania, że użytkownicy mają najnowszą wersję Firefox / Chrome/Opera a kompatybilność starej przeglądarki nie jest problemem.

Author: vqdave, 2011-10-12

5 answers

Będziesz chciał użyć prostego javascript do tego. Powodem jest to, że będziesz chciał stale sondować i nie czekać na wywołania zwrotne. Nie potrzebujesz do tego jQuery, to całkiem proste. Mają jakiś ładny kod źródłowy do tego na stronie Ajax Patterns.

Zasadniczo będziesz chciał śledzić swoją ostatnią pozycję w odpowiedzi i okresowo przepytywać więcej tekstu poza tą lokalizacją. Różnica w Twoim przypadku polega na tym, że możesz zapisz się na całe wydarzenie i zakończ ankietę.

 19
Author: scottheckel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-08 18:03:28

Jest to dość proste przy wysyłaniu tekstu lub HTML. Poniżej znajduje się przykład.

(napotkasz problemy podczas próby wyjścia JSON , które jednak rozwiążę dalej.)

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

PLIK 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>

A co jeśli muszę to zrobić z JSONEM?

Nie jest możliwe stopniowe Ładowanie pojedynczego obiektu JSON (zanim zostanie on w pełni załadowany), ponieważ dopóki nie będziesz mieć całego obiektu, składnia będzie zawsze bądź nieważny.

Ale jeśli Twoja odpowiedź ma wiele obiektów JSON, jeden po drugim, to możliwe jest ładowanie po jednym na raz, gdy schodzą w dół rury.

Więc poprawiłem mój kod powyżej przez...

  1. Zmiana linii 4 pliku PHP z echo $val; na echo '{"name":"'.$val.'"};'. To wyprowadza szereg obiektów JSON.

  2. Zmiana linii 24 pliku HTML z console.log(this_response); na

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

    Zauważ, że ten podstawowy kod zakłada, że każdy "kawałek" przychodzący do przeglądarka jest prawidłowym obiektem JSON. Nie zawsze tak będzie, ponieważ nie możesz przewidzieć, jak pakiety dotrą - być może będziesz musiał podzielić ciąg znaków na półkolonie (lub wymyślić inny znak separatora).

Nie używaj application/json

Czy Nie za zmianę nagłówków na application/json - zrobiłem to i przez 3 dni googlowałem. Gdy typem odpowiedzi jest application/json, przeglądarka czeka aż odpowiedź zostanie ukończona, tak jak w pełni ukończona. Pełna odpowiedź jest następnie przetwarzany, aby sprawdzić, czy jest to JSON. Jednak nasza pełna odpowiedź to {...};{...};{...};, co nie jest poprawne JSON. Metoda jqXHR.done zakłada, że wystąpił błąd, ponieważ kompletna odpowiedź nie może być przetworzona jako JSON.

Jak wspomniano w komentarzach, możesz wyłączyć tę kontrolę po stronie klienta, używając:

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

Mam nadzieję, że niektórym się to przyda.

 50
Author: AlexMorley-Finch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-01 22:16:02

Użyj XMLHttpRequest.js

Https://github.com/ilinsky/xmlhttprequest

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

    XMLHttpRequest 1.0-implementacja obiektu XMLHttpRequest 1.0, zgodna ze standardami W3C]}
  • naprawia wszystkie dziwactwa przeglądarek obserwowane w ich natywnych implementacjach obiektów XMLHttpRequest
  • umożliwia przejrzyste rejestrowanie aktywności obiektu XMLHttpRequest

Aby używać długich sondaży z PHP:

Wyjście.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);
}
Uciekaj.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>

To powinno wyjść:

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 

Dla IE trzeba zajrzeć do 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

 32
Author: Petah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-02 23:47:21

Ponieważ mówisz, że twój serwer jest przyjazny dla strumienia (asynchroniczny) i szukał rozwiązania jquery, sprawdziłeś jQuery Stream plugin?

Jest naprawdę łatwy w użyciu i pozwala naprawdę nie martwić się o wiele czegokolwiek. Ma całkiem nieźle dokumentacja również.

 14
Author: g19fanatic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-01 12:12:52

Oto prosty sposób na osiągnięcie tego za pomocą JQuery (zgodnie z życzeniem OP):

Najpierw rozszerz obiekt ajax o obsługę onreadystatechange, uruchamiając poniższy kod z https://gist.github.com/chrishow/3023092 (załączony na dole tej odpowiedzi). Następnie po prostu wywołaj ajax używając funkcji onreadystatechange, która sprawdzi xhr.responseText dla nowego tekstu.

Jeśli chcesz być jeszcze bardziej fantazyjny, możesz wyczyścić dane responseText za każdym razem, gdy je przeczytasz, takie jak opisane tutaj ).

Na przykład patrz https://jsfiddle.net/g1jmwcmw/1 / , który pobierze odpowiedź z https://code.jquery.com/jquery-1.5.js i wydrukuj go w kawałkach wewnątrz okna konsoli, używając poniższego kodu (który możesz skopiować na stronę html, a następnie otworzyć w przeglądarce):

<!-- 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>
 0
Author: mwag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-06 14:53:49