AngularJS : multiple promise wait all - angularjs

I have a little question about multiple promise.
How can I wait that all promises will be done for return the final result.
See my code :
getInfo : function(){
return promiseA.then(function(result){
info = result;
//this function have also promises
return ServiceA.functionA(info.login)
.then(function(favouriteItems){
info.favorites = favouriteItems;
return $q.when(info);
});
});
},
My aims it's to wait the result of ServiceA.functionA before return value.
Thanks
K.L

I wrote an answer to another question stating the solution to this problem using the $q.all approach.
Check it out: AngularJS: Listen to events, one after the other

You need to use $q.all()
This is a good post here on the question: stackoverflow.com/questions/21310964/angularjs-q-all

function getInfo() {
var deferred = $q.defer();
promiseA.then(function(result){
info = result;
// this function have also promises
ServiceA.functionA(info.login)
.then(function(favouriteItems){
info.favorites = favouriteItems;
// the magic
deferred.resolve(info);
// at this point, you can resolve any value
});
});
}
return deferred.promise;
}
then later you can call that function and get a promise...
var promise = getInfo();
promise.then(successCallback, errorCallback, notifyCallback);
The successCallback will only be called once resolve has been called on the deferred object.

getInfo : function() {
return promiseA.then(function(result){
return ServiceA.functionA(result.login).then(function(favouriteItems){
result.favorites = favouriteItems;
return result;
});
});
},
Use like this:
api.getInfo().then(function(result){
// use result and result.favorites
}, function(err){
// here you can be if an error occurred in promiseA or in ServiceA.functionA
})

Related

a promise for a recursive data structure

I want to resolve a recursive data structure before calling a controller, but I'm struggling with how to do this.
my current code looks like this:
region.loadChildren = function() {
region.children = [];
return region.resource.all('children').getList().then(function(subs) {
angular.forEach(subs, function(sub) {
sub.loadChildren();
region.children.push(sub);
});
}, errorCallback
);
};
can I somehow "collect" the promises returned by sub.loadChildren() and combine them?
Yep, you can use $q.all to gather the promises into a single promise that resolves when each promise in the list has resolved.
var promises = []
angular.forEach(subs, function(sub) {
promises.push(sub.loadChildren());
region.children.push(sub);
});
$q.all(promises).then(function(values) {
// Finished
})
$q.all is youre solution -
region.loadChildren = function() {
region.children = [];
return region.resource.all('children').getList().then(function(subs) {
var promises = subs.map(subs, function(sub) {
var promise = sub.loadChildren();
region.children.push(sub);
return promise;
});
var allPromisesInOnePromise = $q.all(promises); // a regular promise which will resolve when all of the promises will resolve
}, errorCallback
);
};
you can read more about promises here and the $q.all function here- https://docs.angularjs.org/api/ng/service/$q
Use $q.all to consolidate promise collections and be sure to return them for chaining.
region.loadChildren = function() {
//return for chaining
return region.resource.all('children').getList().then(function(subs) {
var children = [];
angular.forEach(subs, function(sub) {
var p = sub.loadChildren();
children.push(p);
});
//return for chaining
return $q.all(children);
);
};
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
For more information, see AngularJS $q service API Reference -- chaining promises.
Be aware that $q.all is not resilient. If one of the promises is rejected, $q.all will resolve rejected with the first error.
You can replace your current function with one that creates the initial array into which the promises will be accumulated, and then pass that to a modified version of your original function that does the real work:
region.loadChildren = function() {
var tmp = [];
this.loadChildrenHelper(tmp);
this.children = tmp;
}
region.loadChildrenHelper = function(accum) {
return region.resource.all('children').getList().then(function(subs) {
angular.forEach(subs, function(sub) {
sub.loadChildrenHelper(accum);
accum.push(sub);
});
}, errorCallback
);
};
and then use $q.all on the resulting array

q.all not working for multiple promises

I have the following q.all calling to resolve two promises. I checked all the posts and tried all other ways of implementation q.all and its the same case
var xyzdeffered = $q.defer();
service1.getServiceDetail1($routeParams.id).then(function(promise) {
xyzdeffered.resolve(promise);
});
var abcdeffered = $q.defer();
service2.getServiceDetail2($routeParams.id).then(function(promise) {
abcdeffered.resolve(promise);
});
$q.all([ xyzdeffered, abcdeffered ]).then(function(data) {
$scope.variable = data;
});
I am expecting the variable in q.all should get populated only after the first two promises are resolved. But unfortunately the service call itself doesn't get returned with data and the control moves on to the q.all. I find this weird because as per documentation the q.all is called only when your promises are returned with 200 response and are resolved.
I checked analysing the network calls and also put some alert to see the sequence of the code and find that the q.all alert is the first alert to be popped up and then the other promises are resolved.
Its really making me mad as why a simple implementation of q.all doesnt work..
Any help will be appreciated.
Why not directly call $q.all on first promise ?
$q.all([
service1.getServiceDetail1($routeParams.id),
service2.getServiceDetail2($routeParams.id)
]).then(function(data) {
//Array of result [resultOfgetServiceDetails1, resultOfgetServiceDetails2]
$scope.variable = data;
});
You need to reference the promise on the $q.defer() return object:
$q.all([ xyzdeffered.promise, abcdeffered.promise ])
For example you have you run multiple sq-lite queries synchronously just pass the array of queries(you can pass anything) as args into the call of this method.
function methodThatChainsPromises(args,tx) {
var deferred = $q.defer();
var chain = args.map(function(arg) {
var innerDeferred = $q.defer();
tx.executeSql(arg,[], function(){
console.log("Success Query");
innerDeferred.resolve(true);
}, function(){
console.log("Error Query");
innerDeferred.reject();
});
return innerDeferred.promise;
});
$q.all(chain).then(
function(results) {
deferred.resolve(true)
console.log("deffered resollve"+JSON.stringify(results));
}, function(errors) {
deferred.reject(errors);
console.log("deffered rejected");
});
return deferred.promise;
}

Angular conditional promises

For an angular project, I have to nest promises and I run into cases where I am not sure of what I am doing.
Here is one of my code :
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
$q.all([Action2(), Action3(), Action4()]).then(function () {
defer.resolve();
});
} else {
defer.reject("error_code");
}
return defer.promise;
});
Action1, Action2, Action3 and Action4 are working promises functions. It's a lot of promises and actions depend on conditions.
Can I do that and be sure my main function will be always resolved or rejected?
I read that we can pass promise inside resolve function.
Can I do that and is this the same as above:
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
defer.resolve($q.all([Action2(), Action3(), Action4()]);
} else {
defer.reject("error_code");
}
return defer.promise;
});
No, it is not. Your first function would stay forever pending if one of Action2(), Action3() or Action4() did "throw", and reject the $q.all(…) promise - your deferred is never resolved then. This is the most common bug of the deferred antipattern you've used here.
Your second function does mitigate this, but is still unncessary complicated. You don't need a deferred here at all! Just return the promise directly, and use $q.reject:
return Action1().then(function (data) {
if (data.condition) {
return $q.all([Action2(), Action3(), Action4()]);
} else {
return $q.reject("error_code");
}
});
Or, as this happens inside a then handler, you can also use throw "error_code".
Thanks for your answer, I can see my error on the first code version. I think it's the q.all which perturbs me.
I read the deferred antipattern. It said that we don't have to create deferred objects for no reason.
The simple case is this :
return Action1().then(function () {
return $q.all([Action2(),Action3(), Action4()]);
});
But due to the if (data.condition) I can't do it.
Is my second code the only way to do it? Am I in a case or I have to use defer?
It speaks about "promisification", but with Angular I don't know if it's a good thing (libs seem unmaintained).
Cheers,

For Loops & Promises

I'm having some trouble managing a for loop with multiple promises. I've read that I should use $q.all, but I havent had any success with it thus far. I need this function to make sure all of the async calls inside the for loop have occurred.
refreshFeed: function(){
var defer = $q.defer();
var promises = []; // array of promises
for (var x = 0; x < userdata.length; x++){
this.getLatest(x).then(function(){
promises.push(x);
});
}
console.log(userdata); // this is updated successfully by getLatest(x)
$q.all(promises).then(function(){
defer.resolve();
})
return defer;
}
It successfully prints the console.log but then I get the console error "undefined is not a function" at the line that initially calls refreshFeed().
Any help would be appreciated. Thank you!
Edit:
getLatest: function(x){
var deferred = $q.defer();
var promise = twitterService.getLatestTweets(userdata[x].searchterm, userdata[x].sources).then(function(data) {
userdata[x].tweets = data;
console.log(userdata[x].tweets);
deferred.resolve();
});
return deferred.promise;
}
Update: I used PSL's answer and toyed around with getLatest and I finally got it to work. I just needed to replace the userdata[x]'s with the function parameter. Thanks #PSL
You would need to pass an array of promises and even if you are using deferred pattern you are not resolving anything. Assuming getLatest returns a promise you could just do:-
refreshFeed: function(){
return $q.all(userdata.map(this.getLatest));
}
Array.map

backbone.fetch using "return" only returns undefined instead of the specific value

Hi i'm new to backbonejs,
I created a function to validate sessions and have if and else statements through my routes when calling this function
var validateSession = function(){
var session = new sessionCollection();
session.fetch( {
success : function(){
return true;
},
error : function(){
CarRentalApp.trigger('admin:login');
return false;
}
} );
};
the problem is the "return" statement is not working out for me. Instead of returning true or false, it returns undefined instead. Am I missing something?
You can also use $.Deferred()
var validateSession = function(){
var defer = $.Deferred();
var session = new sessionCollection();
session.fetch( {
success : function(){
defer.resolve(true);
},
error : function(){
defer.resolve(false);
}
});
return defer.promise();
};
And then:
$.when(validateSession()).done(function(result){
});
I think this is a javascript question rather than backbone.
Backbone.Collection.fetch method is an asynchronous method, that means if you do this:
validateSession();
the function will call session.fetch() and then return immediately, it won't wait the fetch() method finish it's job. you don't have return statement in the validateSession function, so it just return you undefined. and when the fetch() method finished, you won't get any notification.
if you want get the result of session.fetch(), I think you'd better trigger events in both cases of success and error:
var validateSession = function(){
var session = new sessionCollection();
session.fetch( {
success : function(){
CarRentalApp.trigger('valid:true');
},
error : function(){
CarRentalApp.trigger('valid:false');
}
} );
};
then listen on these two events and do some process respectively.
hope this can help.

Resources