jQuery a lu AJAX stream progressivement?

j'ai lu cette question mais elle ne répond pas exactement à ma question. Malheureusement, il semble que les choses ont changé dans L'objet XHR depuis la dernière fois que J'ai regardé AJAX, donc il n'est plus possible d'accéder directement à responseText avant qu'il ne soit terminé.

je dois écrire une page qui utilise AJAX (de préférence jQuery, mais je suis ouvert aux suggestions) pour récupérer des données CSV via HTTP à partir d'un serveur sur lequel je n'ai aucun contrôle. Le les données de réponse pourrait être assez important; un mégaoctet de texte n'est pas rare.

le serveur est convivial. Est-il encore un moyen d'obtenir l'accès à un flux de données qu'il est retourné, directement à partir de JavaScript?

j'ai la possibilité d'écrire un code PHP qui vit au milieu et utilise une sorte de technologie" Comet " (long-polling, EventSource, etc), mais je préférerais éviter cela si possible.

In dans le cas où il est pertinent, supposez pour cette question que les utilisateurs ont la dernière version de Firefox/Chrome/Opera et que la compatibilité avec l'ancien navigateur ne pose pas de problème.

65
demandé sur vqdave 2011-10-12 17:15:18

5 réponses

vous allez vouloir utiliser javascript pour cela. La raison est que vous allez vouloir sonder en permanence et ne pas attendre que les rappels à tirer. Vous n'avez pas besoin de jQuery pour ça, c'est assez simple. Ils ont quelque code source agréable pour cela sur le site Web Ajax Patterns .

essentiellement, vous aurez juste à garder une trace de votre dernière position dans la réponse et de sonder périodiquement pour plus de texte au-delà de cet endroit. Le la différence dans votre cas est que vous pouvez vous inscrire à l'événement complet et arrêter votre vote.

19
répondu scottheckel 2015-12-08 18:03:28

c'est assez simple quand on sort texte ou HTML . Ci-dessous est un exemple.

(vous rencontrerez des problèmes si vous essayez de sortir JSON cependant, que je vais aborder plus loin.)

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

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

et si je dois faire ça avec JSON?

ce n'est pas réellement possibilité de charger un seul objet JSON progressivement (avant qu'il ne soit complètement chargé) parce que jusqu'à ce que vous ayez l'objet complet, la syntaxe sera toujours invalide.

mais si votre réponse a multiple objets JSON, un après l'autre, alors il est possible de charger un à la fois, comme ils descendent le tuyau.

alors j'ai modifié mon code...

  1. changer la ligne de fichier PHP 4 de echo $val; à echo '{"name":"'.$val.'"};' . Cette fonction génère une série d'objets JSON.

  2. changement de la ligne 24 du fichier HTML de console.log(this_response); à

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

    notez que ce code rudimentaire suppose que chaque" morceau " venant au navigateur est un objet JSON valide. Ce ne sera pas toujours le cas parce que vous ne pouvez pas prédire comment les paquets arriveront - vous pourriez avoir besoin de diviser la chaîne basée sur des semi-colons (ou de trouver un autre caractère de séparation).

N'utilisez pas application/json

Do NOT For change your headers to application/json - j'ai fait ceci et il m'a fait googler pendant 3 jours. Lorsque le type de réponse est application/json , le navigateur attend jusqu'à ce que la réponse soit complète, comme dans entièrement complète. La réponse complète est ensuite analysée pour vérifier si elle est infact JSON. Cependant notre réponse complète est {...};{...};{...}; qui n'est pas valide JSON. La méthode jqXHR.done suppose qu'il y a eu une erreur, parce que la réponse complète ne peut pas être considérée comme JSON.

comme mentionné dans les commentaires, vous pouvez désactiver ce contrôle côté client en utilisant:

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

Espère que certaines personnes trouvent cela utile.

50
répondu AlexMorley-Finch 2017-05-01 22:16:02

Utiliser XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

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

  • Offre discret, respectueux des standards (W3C) de la croix-navigateur de mise en œuvre de la XMLHttpRequest 1.0 objet
  • fixe toutes les bizarreries des navigateurs observées dans leurs implémentations d'objets XMLHttpRequest natives
  • permet l'enregistrement transparent de L'activité de L'objet XMLHttpRequest

pour utiliser le long polling avec PHP:

"151930920 de sortie".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);
}

exécuter.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>

Ce qui devrait sortie:

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 

Pour IE, vous devez regarder dans 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
répondu Petah 2011-11-02 23:47:21

puisque vous dites que votre serveur est convivial (asynchrone) et qu'il cherchait une solution jquery, avez-vous vérifié le Plugin jQuery Stream ?

il est vraiment facile à utiliser et vous permet de ne pas vraiment vous soucier de beaucoup de choses. Il a assez bon documentation ainsi.

14
répondu g19fanatic 2011-11-01 12:12:52

voici un moyen simple d'y parvenir en utilisant JQuery (comme demandé par L'OP):

tout d'abord, étendez l'objet ajax pour soutenir onreadystatechange en exécutant le code ci-dessous à partir de https://gist.github.com/chrishow/3023092 (voir au bas de la présente réponse). Ensuite, il suffit d'appeler ajax en utilisant une fonction onreadystatechange qui vérifiera xhr.responsabilité pour le nouveau texte.

si vous vouliez être encore plus fantaisiste, vous pourrait effacer les données respetext chaque fois que vous le lisez, comme décrit ici ).

par exemple, voir https://jsfiddle.net/g1jmwcmw/1 / , qui téléchargera la réponse de https://code.jquery.com/jquery-1.5.js et le sortir en morceaux à l'intérieur de votre fenêtre de console, en utilisant le code ci-dessous (que vous pouvez simplement copier dans une page html puis ouvrir dans votre navigateur):

<!-- 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
répondu mwag 2017-12-06 14:53:49