web-dev-qa-db-de.com

jQuery lesen AJAX inkrementell streamen?

Ich habe diese Frage gelesen, aber sie beantwortet meine Frage nicht genau ... Leider sieht es so aus, als hätten sich die Dinge im XHR-Objekt geändert, seit ich AJAX das letzte Mal angeschaut habe, so dass es nicht mehr direkt möglich ist Greifen Sie auf responseText zu, bevor der Vorgang abgeschlossen ist.

Ich muss eine Seite schreiben, die AJAX verwendet (vorzugsweise jQuery, aber ich bin offen für Vorschläge), um CSV-Daten über HTTP von einem Server abzurufen, über den ich keine Kontrolle habe. Die Antwortdaten könnten ziemlich groß sein. ein Megabyte Text ist nicht ungewöhnlich.

Der Server ist Stream-freundlich. Gibt es immer noch eine Möglichkeit, direkt aus JavaScript auf einen Datenstrom zuzugreifen, wenn er zurückgegeben wird?

Ich habe die Möglichkeit, einen PHP -Code zu schreiben, der in der Mitte lebt und eine Art "Comet" -Technologie verwendet (Long-Polling, EventSource usw.), aber ich würde es vorziehen, wenn möglich, dies zu vermeiden.

Falls dies relevant ist, nehmen Sie für diese Frage an, dass Benutzer über die neueste Version von Firefox/Chrome/Opera verfügen und alte Browserkompatibilität kein Problem darstellt.

66
Josh

Sie möchten Javascript direkt verwenden. Der Grund ist, dass Sie kontinuierlich abfragen möchten und nicht warten sollen, bis die Rückrufe ausgelöst werden. Sie benötigen dafür kein jQuery, es ist ziemlich einfach. Sie haben dafür einen Nice-Quellcode auf der Ajax Patterns-Website .

Im Wesentlichen möchten Sie nur Ihre letzte Position in der Antwort nachverfolgen und regelmäßig nach mehr Text hinter dieser Position abfragen. Der Unterschied besteht in Ihrem Fall darin, dass Sie das vollständige Ereignis abonnieren und die Abfrage beenden können.

19
scottheckel

Dies ist bei der Ausgabe von text oder HTML recht einfach. Unten ist ein Beispiel.

(Wenn Sie versuchen,JSONauszugeben, werden Sie auf Probleme stoßen, die ich weiter unten angehen werde.)

PHP-Datei

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

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

Was ist, wenn ich dies mit JSON machen muss?

Es ist tatsächlich nicht möglich, ein einzelnes JSON-Objekt inkrementell zu laden (bevor es vollständig geladen ist), da die Syntax bis zum vollständigen Objekt immer ungültig ist.

Wenn Ihre Antwort jedoch nacheinander multiple JSON-Objekte enthält, ist es möglich, eines nach dem anderen zu laden, wenn sie die Pipe herunterkommen.

Also habe ich meinen Code oben durch ...

  1. Ändern von PHP FILE Zeile 4 von echo $val; in echo '{"name":"'.$val.'"};'. Dies gibt eine Reihe von JSON-Objekten aus.

  2. Ändern der HTML-DATEI-Zeile 24 von console.log(this_response); in

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

    Beachten Sie, dass dieser rudimentäre Code davon ausgeht, dass jeder "Block", der an den Browser kommt, ein gültiges JSON-Objekt ist. Dies ist nicht immer der Fall, da Sie nicht vorhersagen können, wie Pakete ankommen werden. Möglicherweise müssen Sie die Zeichenfolge nach Semikolons aufteilen (oder ein anderes Trennzeichen eingeben).

Verwenden Sie nicht application/json

Bitte NICHT Dafür ändere deine Kopfzeile in application/json - Ich habe dies getan und ich musste 3 Tage lang googeln. Wenn der Antworttyp application/json ist, wartet der Browser so lange, bis die Antwort vollständig ist. Die vollständige Antwort wird dann analysiert, um zu prüfen, ob es sich um infaktives JSON handelt. Unsere vollständige Antwort ist jedoch {...};{...};{...};, was NICHT gültiger JSON ist. Die jqXHR.done-Methode geht davon aus, dass ein Fehler aufgetreten ist, da die vollständige Antwort nicht als JSON analysiert werden kann.

Wie in den Kommentaren erwähnt, können Sie diese Prüfung auf der Clientseite deaktivieren, indem Sie Folgendes verwenden:

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

Ich hoffe, einige Leute finden das nützlich.

54

Verwenden Sie XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

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

  • Liefert eine unauffällige, standardkonforme (W3C) Browserübergreifende Implementierung des XMLHttpRequest 1.0-Objekts
  • Behebt ALLE Browser-Macken, die in ihren nativen XMLHttpRequest-Objektimplementierungen beobachtet wurden
  • Aktiviert die transparente Protokollierung der XMLHttpRequest-Objektaktivität

So verwenden Sie lange Abfragen mit 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>

Dies sollte ausgeben:

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 

Für IE müssen Sie sich XDomainRequest ansehen 

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

http://msdn.Microsoft.com/de-de/library/cc288060(VS.85).aspx

32
Petah

Da Sie sagen, dass Ihr Server Stream-freundlich (asynchron) ist und nach einer Jquery-Lösung gesucht hat, haben Sie das jQuery Stream Plugin ausgecheckt? 

Es ist wirklich einfach zu bedienen und ermöglicht es Ihnen, sich nicht wirklich um alles zu kümmern. Es hat ziemlich guteDokumentation als auch.

15
g19fanatic

Hier ist ein einfacher Weg, dies mit JQuery (wie vom OP gefordert) zu erreichen:

Erweitern Sie zunächst das ajax-Objekt, um onreadystatechange zu unterstützen, indem Sie den folgenden Code unter https://Gist.github.com/chrishow/3023092 ausführen (am Ende dieser Antwort angehängt). Dann rufen Sie einfach ajax mit einer onreadystatechange-Funktion auf, die xhr.responseText auf neuen Text überprüft.

Wenn Sie noch schicker werden möchten, können Sie die responseText-Daten jedes Mal löschen, wenn Sie sie lesen, wie z. B. hier ).

Siehe beispielsweise https://jsfiddle.net/g1jmwcmw/1/ . Dort wird die Antwort von https://code.jquery.com/jquery-1.5.js heruntergeladen und in ausgegeben Chunks in Ihrem Konsolenfenster mit dem folgenden Code (den Sie einfach in eine HTML-Seite kopieren und dann in Ihrem Browser öffnen können):

<!-- 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
mwag