33 votes

Comment enchaîner les appels ajax en utilisant jquery

J'ai besoin de faire une série de N requêtes 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 demandes (notez que ce n'est pas le cas d'utilisation exact, le code actuel doit s'assurer du succès de l'étape (N-1) avant d'exécuter l'étape suivante) :

$(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 requêtes 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 faire en sorte que l'objet différé les mette en file d'attente pour moi ? J'ai essayé de remplacer done par 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]

Edit :

J'apprécie la suggestion d'utiliser un tableau pour stocker les paramètres de la requête, mais l'objet différé jquery a la capacité de mettre les requêtes 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 demandes dans le tuyau une étape à la fois afin d'utiliser efficacement chaque traversée :

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

31voto

nikoshr Points 17480

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 s'écrire

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 des 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 bricolage pour afficher le log 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 le pipe suivant. Notez que je n'ai fourni que la fonction sccess, 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 afin de construire la chaîne.

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

Note : à partir de jQuery 1.8, deferred.pipe() est déprécié, deferred.then le remplace.

5voto

topkara Points 55

Note : A partir de jquery 1.8 vous pouvez utiliser .then au lieu de .pipe . Le site .then renvoie maintenant une nouvelle promesse et .pipe est déprécié car il n'est plus nécessaire. Voir Spéc. des promesses pour plus d'informations sur les promesses, et le 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 âmes sensibles, mais un peu plus compacte :

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 le code ci-dessus fonctionne. Fondamentalement, il prend deux arguments, un callback et une valeur initiale.

La callback est appliquée itérativement 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 second argument est l'élément courant. L'astuce ici est que la fonction getData() renvoie un promesse différée jquery Le pipe s'assure qu'avant que le getData soit appelé sur l'élément actuel, 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. Elle est alimentée à la première itération de l'exécution de la callback, et s'assure que le getData sur le premier élément est immédiatement appelé.

4voto

ziesemer Points 15594

Je ne sais pas exactement pourquoi vous voulez faire ça, mais gardez une liste de toutes les URLs que vous devez demander, et ne demandez pas la suivante avant que vos success est appelée. I.E., success fera conditionnellement des appels supplémentaires à deferred .

4voto

Jeff Shepler Points 412

Je sais que j'interviens tardivement, mais je pense que votre code original est en grande partie correct, mais qu'il présente deux (peut-être trois) problèmes.

Votre getData(country) est appelé immédiatement à cause de la façon dont vous avez codé le paramètre de votre pipe. De la façon dont vous l'avez, getData() s'exécute immédiatement et le résultat (la promesse de l'ajax, mais la requête http commence immédiatement) est passé comme paramètre à pipe() . Ainsi, au lieu de passer une fonction de rappel, vous passez un objet - ce qui fait que le nouveau différé du pipe est immédiatement résolu.

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 différé du tuyau aura été résolu. En le codant de cette façon, le deuxième problème se pose. Aucun des getData()s ne s'exécutera tant que le deferred maître ne sera pas 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 s'il pourrait les exécuter tous en même temps de toute façon. La documentation indique que les callbacks sont exécutés dans l'ordre, mais comme votre callback renvoie une promesse et s'exécute de manière asynchrone, il se peut qu'ils s'exécutent tous un 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();

2voto

Drew Points 2534

Mise à jour : deferred.pipe est déprécié

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

Vous pouvez continuer à les pipeter jusqu'à ce que les 100 soient faites.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X