web-dev-qa-db-fra.com

Comment chaîner les appels ajax à l'aide de jquery

J'ai besoin de faire une série de requêtes N ajax sans verrouiller le navigateur et je veux utiliser l'objet différé jquery pour y parvenir.

Voici un exemple simplifié avec trois demandes, mais mon programme peut avoir besoin de mettre en file d'attente plus de 100 (notez que ce n'est pas le cas d'utilisation exact, le code réel doit garantir le succès de l'étape (N-1) avant d'exécuter la suivante étape):

$(document).ready(function(){

    var deferred = $.Deferred();

    var countries = ["US", "CA", "MX"];

    $.each(countries, function(index, country){

        deferred.pipe(getData(country));

    });

 });

function getData(country){

    var data = {
        "country": country  
    };


    console.log("Making request for [" + country + "]");

    return $.ajax({
        type: "POST",
        url: "ajax.jsp",
        data: data,
        dataType: "JSON",
        success: function(){
            console.log("Successful request for [" + country + "]");
        }
    });

}

Voici ce qui est écrit dans la console (toutes les demandes sont faites en parallèle et le temps de réponse est directement proportionnel à la taille des données pour chaque pays comme prévu:

Making request for [US]
Making request for [CA]
Making request for [MX]
Successful request for [MX]
Successful request for [CA]
Successful request for [US]

Comment puis-je obtenir l'objet différé pour les mettre en file d'attente pour moi? J'ai essayé de changer fait en pipe mais j'obtiens le même résultat.

Voici le résultat souhaité:

Making request for [US]
Successful request for [US]
Making request for [CA]
Successful request for [CA]
Making request for [MX]
Successful request for [MX]

Modifier:

J'apprécie la suggestion d'utiliser un tableau pour stocker les paramètres de demande, mais l'objet différé jquery a la capacité de mettre les demandes en file d'attente et je veux vraiment apprendre à utiliser cette fonctionnalité à son plein potentiel.

C'est effectivement ce que j'essaie de faire:

when(request[0]).pipe(request[1]).pipe(request[2])... pipe(request[N]);

Cependant, je veux affecter les requêtes dans le tube une étape à la fois afin d'utiliser efficacement chaque traversée:

deferred.pipe(request[0]);
deferred.pipe(request[1]);
deferred.pipe(request[2]);
33
Graham

Avec un objet personnalisé

function DeferredAjax(opts) {
    this.options=opts;
    this.deferred=$.Deferred();
    this.country=opts.country;
}
DeferredAjax.prototype.invoke=function() {
    var self=this, data={country:self.country};
    console.log("Making request for [" + self.country + "]");

    return $.ajax({
        type: "GET",
        url: "wait.php",
        data: data,
        dataType: "JSON",
        success: function(){
            console.log("Successful request for [" + self.country + "]");
            self.deferred.resolve();
        }
    });
};
DeferredAjax.prototype.promise=function() {
    return this.deferred.promise();
};


var countries = ["US", "CA", "MX"], startingpoint = $.Deferred();
startingpoint.resolve();

$.each(countries, function(ix, country) {
    var da = new DeferredAjax({
        country: country
    });
    $.when(startingpoint ).then(function() {
        da.invoke();
    });
    startingpoint= da;
});

Violon http://jsfiddle.net/7kuX9/1/

Pour être un peu plus clair, les dernières lignes pourraient être écrites

c1=new DeferredAjax( {country:"US"} );
c2=new DeferredAjax( {country:"CA"} );
c3=new DeferredAjax( {country:"MX"} );

$.when( c1 ).then( function() {c2.invoke();} );
$.when( c2 ).then( function() {c3.invoke();} );

Avec tuyaux

function fireRequest(country) {
        return $.ajax({
            type: "GET",
            url: "wait.php",
            data: {country:country},
            dataType: "JSON",
            success: function(){
                console.log("Successful request for [" + country + "]");
            }
        });
}

var countries=["US","CA","MX"], startingpoint=$.Deferred();
startingpoint.resolve();

$.each(countries,function(ix,country) {
    startingpoint=startingpoint.pipe( function() {
        console.log("Making request for [" + country + "]");
        return fireRequest(country);
    });
});

http://jsfiddle.net/k8aUj/1/

Edit: Un violon sortant le journal dans la fenêtre de résultat http://jsfiddle.net/k8aUj/3/

Chaque appel de pipe renvoie une nouvelle promesse, qui est à son tour utilisée pour la pipe suivante. Notez que je n'ai fourni que la fonction d'accès, une fonction similaire devrait être fournie pour les échecs.

Dans chaque solution, les appels Ajax sont retardés jusqu'à ce qu'ils soient nécessaires en les enveloppant dans une fonction et une nouvelle promesse est créée pour chaque élément de la liste pour construire la chaîne.

Je crois que l'objet personnalisé fournit un moyen plus facile de manipuler la chaîne, mais les tuyaux pourraient mieux convenir à vos goûts.

Remarque : à partir de jQuery 1.8, deferred.pipe() est déconseillé, deferred.then le remplace.

31
nikoshr

Remarque: Depuis jquery 1.8, vous pouvez utiliser .then Au lieu de .pipe. La fonction .then Renvoie désormais une nouvelle promesse et .pipe Est déconseillé car il n'est plus nécessaire. Voir promises spec pour plus d'informations sur les promesses, et q.js pour une bibliothèque plus propre de promesses javascript sans dépendance jquery.

countries.reduce(function(l, r){
  return l.then(function(){return getData(r)});
}, $.Deferred().resolve());

et si vous aimez utiliser q.js:

//create a closure for each call
function getCountry(c){return function(){return getData(c)};}
//fire the closures one by one
//note: in Q, when(p1,f1) is the static version of p1.then(f1)
countries.map(getCountry).reduce(Q.when, Q());

Réponse originale:

Encore une autre pipe; pas pour les timides, mais un peu plus compact:

countries.reduce(function(l, r){
  return l.pipe(function(){return getData(r)});
}, $.Deferred().resolve());

Réduire la documentation est probablement le meilleur endroit pour commencer à comprendre comment fonctionne le code ci-dessus. Fondamentalement, il prend deux arguments, un rappel et une valeur initiale.

Le rappel est appliqué de manière itérative sur tous les éléments du tableau, où son premier argument est alimenté par le résultat de l'itération précédente, et le deuxième argument est l'élément actuel. L'astuce ici est que la getData() renvoie une jquery deferred promise , et le canal s'assure qu'avant l'appel de getData sur l'élément en cours, le getData de l'élément précédent est terminé.

Le deuxième argument $.Deferred().resolve() est un idiome pour une valeur différée résolue. Il est alimenté à la première itération de l'exécution de rappel et s'assure que le getData sur le premier élément est immédiatement appelé.

5
topkara

Je ne sais pas exactement pourquoi vous voudriez faire cela, mais conservez une liste de toutes les URL que vous devez demander, et ne demandez pas la suivante jusqu'à ce que votre fonction success soit appelée. I.E., success fera des appels supplémentaires conditionnels à deferred.

4
ziesemer

Je sais que je suis en retard à cela, mais je crois que votre code d'origine est généralement bien, mais a deux (peut-être trois) problèmes.

Votre getData(country) est appelée immédiatement en raison de la façon dont vous avez codé le paramètre de votre canal. De la façon dont vous l'avez, getData() s'exécute immédiatement et le résultat (la promesse d'ajax, mais la requête http commence immédiatement) est passé en paramètre à pipe(). Ainsi, au lieu de passer une fonction de rappel, vous passez un objet - ce qui entraîne la résolution immédiate du nouveau différé du tuyau.

Je pense que ça doit être

deferred.pipe(function () { return getData(country); });

Il s'agit maintenant d'une fonction de rappel qui sera appelée lorsque le parent du canal différé aura été résolu. Le coder de cette façon soulèvera le deuxième problème. Aucune des getData () ne s'exécutera jusqu'à ce que le master différé soit résolu.

Le troisième problème potentiel pourrait être que, puisque tous vos tuyaux seraient attachés au maître différé, vous n'avez pas vraiment de chaîne et je me demande si cela pourrait les exécuter tous en même temps de toute façon. Les documents indiquent que les rappels sont exécutés dans l'ordre, mais comme votre rappel renvoie une promesse et s'exécute en mode asynchrone, ils peuvent tous s'exécuter quelque peu en parallèle.

Donc, je pense que vous avez besoin de quelque chose comme ça

var countries = ["US", "CA", "MX"];
var deferred = $.Deferred();
var promise = deferred.promise();

$.each(countries, function(index, country) {
    promise = promise.pipe(function () { return getData(country); });
});

deferred.resolve();
4
Jeff Shepler

J'ai eu du succès avec les files d'attente jQuery.

$(function(){
    $.each(countries, function(i,country){
      $('body').queue(function() {
        getData(country);
      });
    });
});

var getData = function(country){
  $.ajax({
    url : 'ajax.jsp',
    data : { country : country },
    type : 'post',
    success : function() {                          
      // Que up next ajax call
      $('body').dequeue();
    },
    error : function(){
      $('body').clearQueue();
    }
  });
};
3
Daniel Bardi

Mise à jour: deferred.pipe est déconseillé

C'est beaucoup de code pour quelque chose qui est déjà documenté dans l'API jQuery. voir http://api.jquery.com/deferred.pipe/

Vous pouvez simplement les canaliser jusqu'à ce que les 100 soient finis.

Ou, j'ai écrit quelque chose pour passer N appels et résoudre une seule fonction avec les données de tous les appels qui ont été faits. Remarque: il renvoie les données et non le super objet XHR. https://Gist.github.com/1219564

2
Drew