JQuery get rid of nested ajax functions

In my JS, I need to get the contents of 3 files using AJAX, and then make the code. This led to a rather strange looking creation of nested asynchronous functions. Also at any time when I work with asynchronous functions, this ugly nesting appears.

How can I avoid nesting functions when I just want to wait for them to finish? (I use jQuery if this helps)

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". }); }); }); }); } 
0
source share
2 answers

Here are a few different methods with and without deferred. In all cases, all ajax calls are launched, and then some code snippets track when all ajax calls have completed and collect data from the calls as they are completed, so when the latter finishes, all data is available.

You can immediately start all three ajax calls and simply check each completion function if they are still executed, storing the results in a local variable until they are executed:

 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(); }); } 

Or you can add more to the general function and pass an array of file names to the function:

 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)); } } 

Using Deferred, you can do this:

 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 }); } 

Or an even shorter version, using the fact that deferreds store arguments from sucess handlers for each deferred:

 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 }); } 

A working demo of this last option: http://jsfiddle.net/jfriend00/5ppU4/

FYI, inside $.when() there is no magic. If you look at the jQuery code for it, it will just keep the counter when all the arguments passed to it are completed (as in my first two options here). The main difference is that it uses the promise interface for the jqXHR object, instead of knowing that it is an ajax call. But conceptually, he does the same.


Here's another one, using a new object that I wrote to handle several pending events.

 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])); } } 

MultiDeferred code is a jQuery plugin specially written to process notifications about you when several pending records are executed, and the code for it:

 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 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; }; 
+3
source

Create an array of each required file, then loop through the array of files and call $.get each iteration and call the join function, which will combine the data and check the counter check as soon as the count reaches the callback,

 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<files.length; i++){ $.get(files[i],combineFile); } } loadData(["files1.txt","files2.txt","files3.txt"],function(data){ console.log("Combined Data: ",data); }); 
0
source

All Articles