I'm trying to figure out how to acplish this workflow, but can't seem to nail it. I've got n number of <select>
elements on a page. When the page loads, for each <select>
element, I need to make a $.get(...);
call. Once all of those calls are done, then, and only then do I need to run an additional function. Here is some example code to better explain:
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
// Do something to selectEl with the result
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
// Once *all* the $.get(...) calls are done, do more things
doMoreWork();
});
Using the code above, doMoreWork()
is usually called before all of the async $.get(...);
calls have had a chance to return; which is not what I want. I need to have all of the $.get(...);
calls plete before doMoreWork()
can be called. Basically I need a callback of sorts to execute once ALL of the $.get(...);
calls in the above example have finished.
How would I go about acplishing this?
I'm trying to figure out how to acplish this workflow, but can't seem to nail it. I've got n number of <select>
elements on a page. When the page loads, for each <select>
element, I need to make a $.get(...);
call. Once all of those calls are done, then, and only then do I need to run an additional function. Here is some example code to better explain:
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
// Do something to selectEl with the result
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
// Once *all* the $.get(...) calls are done, do more things
doMoreWork();
});
Using the code above, doMoreWork()
is usually called before all of the async $.get(...);
calls have had a chance to return; which is not what I want. I need to have all of the $.get(...);
calls plete before doMoreWork()
can be called. Basically I need a callback of sorts to execute once ALL of the $.get(...);
calls in the above example have finished.
How would I go about acplishing this?
Share Improve this question asked Aug 24, 2011 at 19:19 ckittelckittel 6,6464 gold badges42 silver badges71 bronze badges 1- Use deferred.then() – epascarello Commented Aug 24, 2011 at 19:24
7 Answers
Reset to default 5Every time you call
doWork
, increment a counter.Every time a response es back, decrement the counter.
Have the callback invoke
doMoreWork
when the counter reaches0
.
var counter = 0;
function doWork(selectEl) {
counter++;
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
counter--;
if( !counter ) { doMoreWork(); }
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
});
I would write a class something like:
function synchronizer(query, action, cleanup) {
this.query = query;
this.action = action;
this.cleanup = cleanup;
this.remaining = query.length;
this.plete = function() {
this.remaining -= 1;
if (this.remaining == 0) { this.cleanup(query); }
}
this.run = function() {
query.each(function(index, which) { action(which, this.plete); })
}
}
// Aargh. Expecting doWork() to call a passed-in continuation seems ugly to me
// as opposed to somehow wrapping doWork within the synchronizer... but I can't
// think of a way to make that work.
function doWork(element, next) {
var getData = ...; // build request data based on element
$.get('/foo/bar', getData, function(data) {
// Do something to element with the result, and then
next();
});
}
function doMoreWork(elements) {
// Do something with all the selects now that they are ready
}
new synchronizer($('select'), doWork, doMoreWork).run();
Keep track of how many Ajax calls have yet to plete, and execute doMoreWork()
when there are none left.
$(function(){
var workLeft = $('select').length;
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
// Do something to selectEl with the result
// If done all work
if(!(--workLeft)){
doMoreWork();
}
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
});
You may also want to catch ajax errors.
You can use jQuery's $.when
to join together multiple Deferred objects to one:
$.when.apply($, $('select').map(function(index, selectEl) {
return $.ajax(....);
}).get()).done(function() {
// All AJAX calls finished
});
Basically, $.when
takes multiple Deferred objects as each argument and wraps them together as one Deferred by keeping track of the number of pleted sub-deferres, similar to how a couple of the answers here implemented it manually.
A more readable version of the above code is:
var requests = [];
$('select').each(function(index, selectEl) {
request.push($.ajax(....));
}
$.when.apply($, requests).done(function() {
// All AJAX calls finished
});
Maybe you could use the JavaScript underscore library's after function.
(note: I haven't tested this code)
var numberOfSelectElements = n;
var finished = _after(numberOfSelectElements, doMoreWork);
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
finished();
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
});
Use Deferred:
function doWork(selectEl) {
var getData = ...;
// return Deferred object
return $.get('/foo/bar', getData, function (data) {
});
}
var selects = $('select');
function doItem(i) {
if(selects.length === i) return doMoreWork(); // if no selects left, abort and do more work
$.when(doWork(selects.get(i)).then(function() { // fetch and do next if pleted
doItem(i + 1);
});
});
doItem(0); // start process
Since it looks like you're doing jQuery, you could use the $.ajaxStop event handler... http://api.jquery./ajaxStop/
EDIT Said $.ajaxComplete instead of the correct $.ajaxStop... Fixed now...