Comment enchaîner les appels ajax en utilisant jquery

j'ai besoin de faire une série de requêtes ajax sans verrouiller le navigateur, et je veux utiliser l'objet différé jquery pour accomplir ceci.

voici un exemple simplifié avec trois requêtes, mais mon programme peut avoir besoin d'une file d'attente de plus de 100 (notez que ce n'est pas le cas d'utilisation exact, le code réel doit assurer le 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 demandes sont faites en parallèle et le temps de réponse est directement proportionnelle à 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 faire pour que l'objet différé fasse la queue pour moi? J'ai essayé de changer done to pipe mais j'ai eu 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 requête, mais l'objet jQuery deferred a la capacité de faire la file d'attente des requêtes 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 assigner les requêtes 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]);
33
demandé sur George Stocker 2011-12-23 10:24:42

6 réponses

avec un objet sur mesure

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 pipes

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 le 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 sccess, une fonction similaire devrait être fournie pour les pannes.

Dans chaque solution, les appels Ajax sont retardés jusqu'à ce que nécessaire, les envelopper dans une fonction et une nouvelle promesse est créé pour chaque élément de la liste pour construire la chaîne.

I croyez que l'objet personnalisé fournit un moyen plus facile de manipuler la chaîne, mais les pipes pourraient mieux répondre à vos goûts.

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

30
répondu nikoshr 2013-06-07 11:01:28

Note: à partir de jquery 1.8, Vous pouvez utiliser .then au lieu de .pipe . La fonction .then renvoie maintenant une nouvelle promesse et .pipe est dépréciée car elle n'est plus nécessaire. Voir promesses spec pour plus d'informations sur les promesses, et les q.js pour un nettoyeur bibliothèque de javascript promesses sans jquery dépendance.

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 un autre tuyau; pas pour les cœurs faibles, 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 le code ci-dessus fonctionne. Fondamentalement, il faut deux arguments, un callback et une valeur initiale.

le callback est appliqué itérativement sur tous les éléments du tableau, où son premier argument est nourri le résultat de l'itération précédente, et le deuxième argument est l'élément courant. Le truc ici est que le getData() renvoie un jQuery deferred promise , et le tuyau fait en sorte qu'avant que le getData soit appelé sur l'élément courant le getData de l'élément précédent soit terminé.

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

5
répondu topkara 2014-04-15 12:56:52

Je ne sais pas exactement pourquoi vous voulez faire cela, mais gardez une liste de toutes les URLs que vous devez demander, et ne demandez pas la prochaine Jusqu'à ce que votre fonction success soit appelée. Par exemple, success fera des appels supplémentaires conditionnels à deferred .

4
répondu ziesemer 2011-12-23 06:29:21

je sais que je suis en retard pour ça, mais je crois que votre code original est plutôt bien mais a 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. 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() . Donc au lieu de passer une fonction de rappel, vous passez un objet - ce qui fait que la nouvelle conduite reportée à être immédiatement résolu.

je pense qu'elle doit être

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

maintenant c'est une fonction de rappel qui sera appelée quand le parent de la pipe a été résolu. Le codage de cette façon soulèvera le deuxième problème. Aucun des getData () s NE s'exécute tant que le fichier maître différé n'est pas résolu.

le troisième problème potentiel pourrait être que puisque tous vos tuyaux seraient attachés à la maître différé, vous n'avez pas vraiment de chaîne et je me demande si elle pourrait tous les exécuter en même temps de toute façon. Les docs disent que les callbacks sont exécutés dans l'ordre, mais puisque votre callback renvoie une promesse et exécute async, ils pourraient tous être exécutés 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();
4
répondu Jeff Shepler 2014-04-15 12:53:25

mise à jour: reportée.la pipe est obsolète

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 juste les Piper jusqu'à ce que les 100 sont faites.

ou, 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: il renvoie les données et non l'objet super XHR. https://gist.github.com/1219564

2
répondu Drew 2013-06-07 10:51:47

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();
    }
  });
};
2
répondu Daniel Bardi 2013-07-03 17:32:44