Reputation: 2390
I've spent far too many hours searching for similar questions and trying solutions, so I hope someone has a solution.
Basically, I would like to be notified when a function a() has completed. The problem is that the function contains an ajax call and a loop that calls b(), which again contains an ajax call.
UPDATED WITH FIDDLE: http://jsfiddle.net/hsyj7/1/
Like so:
// called by main()
function a() {
return $.ajax("http://url1").pipe(function(data){
for (var i = 0; i < 2; i++) {
console.log('a called');
b();
}
});
}
// called by a()
function b() {
for (var i = 0; i < 2; i++) {
$.ajax("http://url2", function(data){
// do something
console.log('b called');
}
}
}
function main(){
$.when(a()).done(function(){
console.log('all completed');
});
}
What I would like to see then is, possibly with both calls to a() at the top:
a called
b called
b called
a called
b called
b called
all completed
Instead I get
a called
all completed
b called
b called
Or some variant thereof.
I am aware that the above code is missing defer functionality in both the loop and in b(). In some of the variants I have tried, the done() handler in main() is never called.
Any one know how to do this?
Upvotes: 31
Views: 12087
Reputation: 664297
The question might be old, but since there's no correct solution yet I'll put an answer here. It properly chains the promises by using .then
(previsouly been .pipe
) to achieve the requested result:
function a() {
return $.ajax("http://url1").done(function(data){
console.log('a called');
}).then(function(){
return $.when(b(), b()); // no loop for simplicity
});
}
function b() {
return $.ajax("http://url2").done(function(data){
console.log('b called');
});
}
function main(){
a().done(function(){
console.log('all completed');
}, function() {
console.log('an error occured!');
});
}
Depending on which result data should be available where the nesting/structure might be changed, but the overall ordering is correct.
Upvotes: 2
Reputation: 56467
Yeah, using Deferred
is the way to do that:
function a() {
var def = $.Deferred();
$.ajax("http://url1").done(function(data){
var requests = [];
for (var i = 0; i < 2; i++) {
requests.push(b());
}
$.when.apply($, requests).then(function() { def.resolve(); });
});
return def.promise();
}
// called by a()
function b() {
var def = $.Deferred(),
requests = [];
for (var i = 0; i < 2; i++) {
requests.push($.ajax("http://url2").done(function(data){
// do something
console.log('b called');
});
}
$.when.apply($, requests).then(function() { def.resolve(); });
return def.promise();
}
function main(){
$.when(a()).done(function(){
console.log('all completed');
});
}
//EDIT: Replaced .pipe
with .done
.
Upvotes: 30
Reputation: 235972
You could use an Array which is located in a higher context to push Promise / Deferred objects into. Then you could then use jQuery.when
alongside Function.prototype.apply
to pass all entries as arguments.
(function() {
var promises = [ ],
when = Function.prototype.apply.bind( jQuery.when, null );
function a() {
promises.push($.ajax("http://url1").pipe(function(data){
for (var i = 0; i < 2; i++) {
console.log('a called');
b();
}
}));
return promises;
}
function b() {
for (var i = 0; i < 2; i++) {
promises.push($.ajax("http://url2", function(data) {
// do something
console.log('b called');
}));
}
}
function main() {
promises = [ ];
when( a() ).done(function(){
console.log('all completed');
});
}
}());
Upvotes: 2
Reputation: 28064
I believe this can be fixed with callbacks, but a fiddle would have really helped me check for you.
// called by main()
function a(callback) {
//set this to the number of loops that is going to happen
var number = 2;
return $.ajax("http://url1", function(data){
console.log('a called');
for (var i = 0; i < number ; i++) {
b();
if(number===i){
callback();
}
}
}
}
function main(){
a(function(){
//Function or code you want to run on completion..
});
}
Forgive me if this doesn't work, but i think its the right direction.
Upvotes: 1