web-dev-qa-db-fra.com

jQuery lu AJAX diffuser progressivement?

J'ai lu cette question mais cela ne répond pas exactement à ma question . Malheureusement, il semble que les choses ont changé dans l'objet XHR depuis que j'ai regardé AJAX pour la dernière fois. accédez responseText avant la fin du remplissage.

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

Le serveur est convivial pour les flux. Existe-t-il encore un moyen d’avoir accès à un flux de données lorsqu’il est renvoyé, directement à partir de JavaScript?

J'ai le choix d'écrire du code PHP qui se trouve au milieu et utilise une sorte de technologie "Comet" (longue interrogation, EventSource, etc.), mais je préférerais éviter cela si possible.

Si cela est pertinent, supposons pour cette question que les utilisateurs disposent de la dernière version de Firefox/Chrome/Opera et que la compatibilité des anciens navigateurs ne pose pas de problème.

66
Josh

Vous allez vouloir utiliser javascript directement pour cela. La raison en est que vous allez vouloir interroger en permanence et ne pas attendre que les rappels se déclenchent. Vous n'avez pas besoin de jQuery pour cela, c'est assez simple. Ils ont quelques Nice code source pour cela sur le site Ajax Patterns .

Pour l’essentiel, vous souhaiterez simplement garder une trace de votre dernière position dans la réponse et interroger périodiquement d’autres textes postérieurs à cette position. La différence dans votre cas est que vous pouvez vous abonner à l'événement complet et arrêter votre interrogation.

19
scottheckel

Ceci est assez simple lors de la sortie de texte ou HTML . Ci-dessous un exemple.

(Vous rencontrerez des problèmes si vous essayez de générerJSONcependant, ce que je vais aborder plus bas.)

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>

Que faire si j'ai besoin de faire cela avec JSON?

En réalité, il n'est pas possible de charger un seul objet JSON de manière incrémentielle (avant qu'il ne soit complètement chargé), car tant que vous ne disposez pas de l'objet complet, la syntaxe sera toujours non valide.

Mais si votre réponse contient plusieurs objets JSON, l'un après l'autre, vous pouvez en charger un à la fois, au fur et à mesure de leur descente.

Alors j'ai modifié mon code ci-dessus en ...

  1. Changement de PHP FILE line 4 de echo $val; à echo '{"name":"'.$val.'"};'. Cela génère une série d'objets JSON.

  2. Modification de la ligne 24 du fichier HTML de console.log(this_response); en

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

    Notez que ce code rudimentaire suppose que chaque "morceau" qui parvient au navigateur est un objet JSON valide. Ce ne sera pas toujours le cas car vous ne pouvez pas prédire comment les paquets arriveront. Vous devrez peut-être scinder la chaîne en fonction des points-virgules (ou créer un autre caractère de séparation).

Ne pas utiliser application/json

Ne PAS Pour changer vos en-têtes en application/json - Je l'ai fait et cela m'a fait googler pendant 3 jours. Lorsque le type de réponse est application/json, le navigateur attend que la réponse soit complète, comme s'il était complètement terminé. La réponse complète est ensuite analysée pour vérifier s’il s’agit bien de JSON. Cependant, notre réponse PLEINE est {...};{...};{...};, ce qui n'est PAS un JSON valide. La méthode jqXHR.done suppose une erreur car la réponse complète ne peut pas être analysée en tant que JSON.

Comme mentionné dans les commentaires, vous pouvez désactiver cette vérification côté client en utilisant:

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

J'espère que certaines personnes trouvent cela utile.

54
AlexMorley-Finch

Utilisez XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

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

  • Fournit une implémentation multi-navigateur discrète et conforme à la norme (W3C) de l'objet XMLHttpRequest 1.0
  • Corrige TOUS les aléas des navigateurs observés dans leurs implémentations natives d'objets XMLHttpRequest.
  • Active la journalisation transparente de l'activité de l'objet XMLHttpRequest

Pour utiliser les longues interrogations avec 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>

Cela devrait produire:

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 examiner 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
Petah

Puisque vous dites que votre serveur est compatible avec les flux (asynchrone) et que vous recherchez une solution jQuery, avez-vous vérifié le jQuery Stream Plugin

Il est vraiment facile à utiliser et vous permet de ne pas trop vous soucier de rien. Il a plutôt bondocumentation aussi.

15
g19fanatic

Voici un moyen simple de réaliser cela à l'aide de JQuery (comme demandé par l'OP):

Tout d'abord, étendez l'objet ajax pour qu'il prenne en charge onreadystatechange en exécutant le code ci-dessous à partir de https://Gist.github.com/chrishow/3023092 (ajouté au bas de cette réponse). Ensuite, appelez simplement ajax en utilisant une fonction onreadystatechange qui vérifiera xhr.responseText pour le nouveau texte.

Si vous voulez être encore plus sophistiqué, vous pouvez effacer les données de responseText à chaque fois que vous les 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 l'éditera dans morceaux dans la fenêtre de votre console, en utilisant le code ci-dessous (que vous pouvez simplement copier dans une page html et ensuite 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
mwag