Comment chaîner des appels ajax en utilisant jquery

J’ai besoin de faire une série de N requêtes ajax sans verrouiller le navigateur, et je souhaite utiliser l’object différé jQuery pour accomplir cela.

Voici un exemple simplifié avec trois demandes, mais mon programme devra peut-être mettre plus de 100 fois en queue (notez que ce n’est pas le cas d’utilisation exact, le code lui-même doit garantir le succès de l’étape (N-1) avant l’exécution de la prochaine étape):

$(document).ready(function(){ var deferred = $.Deferred(); var counsortinges = ["US", "CA", "MX"]; $.each(counsortinges, 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 effectuées 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’object différé pour les mettre en queue pour moi? J’ai essayé de changer en pipe mais d’obtenir 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 parameters de requête, mais l’object différé jQuery a la capacité de mettre les requêtes en queue et je souhaite 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 tube une étape à la fois afin d’utiliser efficacement chaque traversée:

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

Avec un object 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 counsortinges = ["US", "CA", "MX"], startingpoint = $.Deferred(); startingpoint.resolve(); $.each(counsortinges, function(ix, country) { var da = new DeferredAjax({ country: country }); $.when(startingpoint ).then(function() { da.invoke(); }); startingpoint= da; }); 

Fiddle 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 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 counsortinges=["US","CA","MX"], startingpoint=$.Deferred(); startingpoint.resolve(); $.each(counsortinges,function(ix,country) { startingpoint=startingpoint.pipe( function() { console.log("Making request for [" + country + "]"); return fireRequest(country); }); }); 

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

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

Chaque appel de canal renvoie une nouvelle promesse, qui est utilisée pour le canal suivant. Notez que je n’ai fourni que la fonction sccess, une fonction similaire doit être fournie pour les échecs.

Dans chaque solution, les appels Ajax sont retardés jusqu’à leur utilisation en les encapsulant dans une fonction et une nouvelle promesse est créée pour chaque élément de la liste afin de créer la chaîne.

Je pense que l’object personnalisé fournit un moyen plus facile de manipuler la chaîne, mais les tuyaux pourraient mieux répondre à vos goûts.

Remarque : à partir de la version 1.8, deferred.pipe() est obsolète, deferred.then remplace.

Remarque: A partir de la version 1.8, vous pouvez utiliser .then place de .pipe . La fonction .then renvoie maintenant une nouvelle promesse et .pipe est obsolète car il n’est plus nécessaire. Consultez prom spec pour plus d’informations sur les promesses et le fichier q.js pour une bibliothèque plus propre de promesses javascript sans dépendance à jquery.

 counsortinges.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) counsortinges.map(getCountry).reduce(Q.when, Q()); 

Réponse originale:

Encore une autre pipe; pas pour les âmes sensibles, mais un peu plus compact:

 counsortinges.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 faut 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 second argument est l’élément en cours. L’astuce ici est que getData() renvoie une promesse différée jQuery et que le canal s’assure qu’avant l’appel de getData sur l’élément en cours, 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 du rappel et s’assure que l’appel à getData du premier élément est immédiatement appelé.

Je ne sais pas exactement pourquoi vous voudriez le faire, mais conservez une liste de toutes les URL que vous devez demander, et ne demandez pas la suivante tant que votre fonction de success n’aura pas été appelée. IE, le success fera conditionnellement faire des appels supplémentaires à deferred .

Je sais que je suis en retard par rapport à cela, mais je pense que votre code d’origine est généralement correct, mais que vous avez deux (peut-être trois) problèmes.

Votre getData(country) est appelé immédiatement en raison de la manière dont vous avez codé le paramètre de votre canal. getData() vous l’avez, getData() s’exécute immédiatement et le résultat (la promesse de ajax, mais la requête http commence immédiatement) est passé en tant que paramètre à pipe() . Ainsi, au lieu de transmettre une fonction de rappel, vous transmettez un object, ce qui entraîne la résolution immédiate du nouveau report du canal.

Je pense que ça doit être

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

C’est maintenant une fonction de rappel qui sera appelée lorsque le parent du canal différé aura été résolu. Le codage de cette façon soulèvera le deuxième problème. Les getData () ne seront exécutés que lorsque le maître différé sera 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. Les docs disent que les callbacks sont exécutés dans l’ordre, mais puisque votre callback renvoie une promesse et s’exécute en async, ils peuvent toujours s’exécuter un peu en parallèle.

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

 var counsortinges = ["US", "CA", "MX"]; var deferred = $.Deferred(); var promise = deferred.promise(); $.each(counsortinges, function(index, country) { promise = promise.pipe(function () { return getData(country); }); }); deferred.resolve(); 

Mise à jour: deferred.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 simplement les garder jusqu’à ce que tous les 100 soient fabriqués.

Ou alors, j’ai écrit quelque chose pour passer N appels et résoudre une seule fonction avec les données de tous les appels passés. Remarque: il renvoie les données et non l’object super XHR. https://gist.github.com/1219564

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

 $(function(){ $.each(counsortinges, 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(); } }); };