jQuery se débarrasse des fonctions ajax nestedes

Dans mon JS, je dois obtenir le contenu de 3 fichiers avec AJAX, puis créer du code. Cela a conduit à une création plutôt étrange de fonctions asynchrones nestedes. De plus, chaque fois que je travaille avec des fonctions asynchrones, cette imbrication laide apparaît.

Comment puis-je éviter les fonctions d’imbrication quand je veux vraiment attendre qu’elles soient terminées? (J’utilise jQuery si cela peut aider)

function loadFilesAndDoStuff() { $.get(firstFile, function(first_file_data) { $.get(secondFile, function(second_file_data) { $.get(thirdFile, function(third_file_data) { someOtherAsyncFunction(function(combined_file_data) { // do some stuff with the "combined_file_data". }); }); }); }); } 

Voici différentes techniques avec et sans utilisation de différés. Dans tous les cas, tous les appels ajax sont lancés, puis un morceau de code garde une trace de la fin de tous les appels ajax et collecte les données des appels au fur et à mesure de leur achèvement, de sorte que toutes les données soient disponibles à la fin du dernier.

Vous pouvez lancer les trois appels ajax en même temps et simplement enregistrer chaque fonction d’achèvement si tout est déjà terminé, en enregistrant les résultats dans une variable locale jusqu’à ce qu’ils soient tous terminés:

 function loadFilesAndDoStuff() { var cntr = 3; var data1, data2, data3; function checkDone() { --cntr; if (cntr === 0) { // all three are done here someOtherFunction(combined_file_data); } } $.get(firstFile, function(data) { data1 = data; checkDone(); }); $.get(secondFile, function(data) { data2 = data; checkDone(); }); $.get(thirdFile, function(data) { data3 = data; checkDone(); }); } 

Ou, vous pouvez en mettre plus dans une fonction commune et passer le tableau de noms de fichiers à la fonction:

 function loadFilesAndDoStuff(filesArray) { var results = []; var doneCnt = 0; function checkDone(index, data) { results[index] = data; ++doneCnt; if (doneCnt === filesArray.length) { // all results are in the results array now } } for (var i = 0; i < filesArray.length; i++) { results.push(null); $.get(filesArray[i], checkDone.bind(this, i)); } } 

En utilisant différé, vous pouvez faire ceci:

 function loadFilesAndDoStuff(filesArray) { var results = []; var deferreds = []; function doneOne(index, data) { results[index] = data; } for (var i = 0; i < filesArray.length; i++) { results.push(null); deferreds.push($.get(filesArray[i], doneOne.bind(this, i))); } $.when.apply($, deferreds).done(function() { // all ajax calls are done and results are available now }); } 

Ou, une version encore plus courte utilisant le fait que différé enregistre les arguments des gestionnaires de succès pour chaque différé:

 function loadFilesAndDoStuff(filesArray) { var deferreds = []; for (var i = 0; i < filesArray.length; i++) { deferreds.push($.get(filesArray[i])); } $.when.apply($, deferreds).done(function() { // all ajax calls are done and results are available now // arguments[0][0] is the data from the first $.get call // arguments[1][0] is the data from the second $.get call // and so on }); } 

Démo de travail de cette dernière option: http://jsfiddle.net/jfriend00/5ppU4/

Pour info, il n'y a pas de magie à l'intérieur de $.when() . Si vous regardez le code jQuery pour cela, c'est juste garder un compteur de quand les arguments qui lui sont passés sont tous faits (semblable à mes deux premières options ici). La principale différence est qu’il utilise l’interface promise de l’object jqXHR au lieu de savoir qu’il s’agit d’un appel ajax. Mais conceptuellement, c'est faire la même chose.


Voici un autre utilisant un nouvel object que j'ai écrit pour gérer plusieurs différés.

 function loadFilesAndDoStuff(filesArray) { var deferred = $.MultiDeferred().done(function() { // all ajax calls are done and results are available now // arguments[0][0] is the data from the first $.get call // arguments[1][0] is the data from the second $.get call // and so on }); for (var i = 0; i < filesArray.length; i++) { deferred.add($.get(filesArray[i])); } } 

Le code MultiDeferred est un plug-in jQuery spécialement conçu pour vous avertir lorsque plusieurs reports sont effectués et que le code correspondant est disponible ici:

 jQuery.MultiDeferred = function(/* zero or more promises */) { // make the Deferred var self = jQuery.Deferred(); var remainingToFinish = 0; var promises = []; var args = []; var anyFail = false; var failImmediate = false; function _add(p) { // save our index in a local variable so it's available in this closure later var index = promises.length; // save this promise promises.push(p); // push placeholder in the args array args.push([null]); // one more waiting to finish ++remainingToFinish; // see if all the promises are done function checkDone(fail) { return function() { anyFail |= fail; // make copy of arguments so we can save them args[index] = Array.prototype.slice.call(arguments, 0); --remainingToFinish; // send notification that one has finished self.notify.apply(self, args[index]); // if all promises are done, then resolve or reject if (self.state() === "pending" && (remainingToFinish === 0 || (fail && failImmediate))){ var method = anyFail ? "reject" : "resolve"; self[method].apply(self, args); } } } // add our own monitors so we can collect all the data // and keep track of whether any fail p.done(checkDone(false)).fail(checkDone(true)); } // add a new promise self.add = function(/* one or more promises or arrays of promises */) { if (this.state() !== "pending") { throw "Can't add to a deferred that is already resolved or rejected"; } for (var i = 0; i < arguments.length; i++) { if (arguments[i] instanceof Array) { for (var j = 0; j < arguments[i].length; j++) { _add(arguments[i][j]); } } else { _add(arguments[i]); } } return this; } // get count of remaining promises that haven't completed yet self.getRemaining = function() { return remainingToFinish; } // get boolean on whether any promises have failed yet self.getFailYet = function() { return anyFail; } self.setFailImmediate = function(failQuick) { failImmediate = failQuick; return this; } // now process all the arguments for (var i = 0; i < arguments.length; i++) { self.add(arguments[i]); } return self; }; 

Créez un tableau de chaque fichier nécessaire, puis parcourez le tableau de fichiers et appelez $.get chaque itération $.get -lui d’appeler une fonction de combinaison qui combinera les données et effectuera une vérification du nombre, une fois que le nombre est rappelé.

 function loadData(files,callback){ var combinedData = ""; var count = 0; function combineFile(data){ count++; combinedData += data; if(count==files.length-1){ callback(combinedData); } } for(var i=0; i