Foreach into foreach with $http promise - angularjs

I'm trying to create an object by forEach on the responses of various services .. and I do not get the results I need.
I know I should use internal iterations promises but do not know how yet ..
EDIT:
well .. I need is to do is to fill an object of the data into $ foreach foreach .. http into a iteration promises, sample code:
someservice1.getitem().then(function(){
var dataInfo= {};
angular.foreach(data, function(v, k){
dataInfo[v]=[]
someservice2.getitem2(k.data).then(function(data){
datainfo[v].push(data)
})
})
$scope.dataInfo = datainfo;
})
so not working very well .. I think you can solve by using promises but I could not do it.
I hope your help

The problem is that the final $scope.dataInfo = datainfo; is executing early before the datainfo object gets completed. You need to create a promise from which that operation can chain safely.
finalPromise.then ( function (datainfo) {
$scope.dataInfo = datainfo;
});
You create finalPromise by returning and chaining from previous promises.
var service1Promise =
someservice1.getitem().then(function(data){
var promiseList = [];
angular.foreach(data, function(v,k){
var p = someservice2.getitem2(k.data);
p.then(function(data) {
//return key and data for chaining
return { key: k, data: data };
}) .catch (function (error) {
//throw key and error to chain rejection
throw { key: k, error: error };
});
//push to list
promiseList.push(p);
});
//return $q.all promise for chaining
return $q.all(promiseList);
});
Create finalPromise by chaining from service1Promise
var finalPromise =
service1Promise.then(function(fulfilledList) {
var dataInfoObj = {};
//assemble data info
angular.forEach(fulfilledList, function (fulfilledItem) {
dataInfoObj[fulfilledItem.key] = fulfilledItem.data;
});
//return datainfoObj for chaining
return datainfoObj;
});
Now you can chain from the finalPromise to put the data on $scope
finalPromise.then ( function onFulfulled (datainfo) {
$scope.dataInfo = datainfo;
}).catch (function onRejection (error) {
console.log("finalPromise rejected");
console.log(error.key);
console.log(error.error);
});
Be aware that $q.all is not resilient. If any of the promises are rejected, $q.all will be rejected with the first rejection.
For more information on chaining promises see the AngularJS $q Service API Reference -- chaining promises.

You are using angularjs, so with $q, for array of promises within promises you could make your code into something like this:
// first add $q service to your controller,
someservice1.getitem()
.then(function(data){
var dataInfo= {};
var promises = angular.map(data, function(item, index){
var promises2 = angular.map(item.data, function(iData){
someservice2.getitem2(iData)
})
return $q.all(promises2).then(function(resultArray){
dataInfo[index] = resultArray;
});
});
return $q.all(promises).then(function(){
console.log('all data are retrived...');
$scope.dataInfo = datainfo;
});
});
the same might look more elegant in ES6:
let serviceCall2 = data => someservice2.getitem2(data); // serviceCall2 is just a closure for someservice2.getitem2() call
someservice1.getitem()
.then(data => {
let dataInfo= {},
promises = data.map((item, index) => {
let promises2 = item.data.map(serviceCall2);
return $q.all(promises2).then(resultArray => dataInfo[index] = resultArray);
});
return $q.all(promises)
.then(() => {
console.log('all data are retrived...');
$scope.dataInfo = datainfo;
});
});

Related

How to get .notify() in chaining promises from $q.all?

I am using chained promises in angular js with $q service and it's working fine except the information of progressCallback ? let me draw what I have done so far?
calling function from my controller in below chainable promise way
fun1()
.then(resp1){
return fun2(resp1.id);
})
.then(resp2){
return $q.all([fun3(resp2.id),fun4(resp2.name)]);
})
.then(function(resp34){
return fun5();
})
.then(success)
.catch(errorhandler)
.finally(final);
and here is my all functions signature in service
var funX = function(param) {
var d = $q.defer();
d.notify('start with funX'); // Note: it was not working so placed inside else
doSomethingASync(param, function(err,data) {
if(err) { d.reject(err);}
else { d.notify('done with funX'); d.resolve(data); }
});
return d.promise;
});
Now my question is where do I receive this d.notify() message in my controller?
trial 1
.then(resp1, info1){
return fun2(resp1.id);
});
but it's undefined
trial 2
.then(resp1, err1, info1) {
return fun2(resp1.id);
}
but still undefined?
UPDATE
I have find a way by adding second parameter in finally()
.then().catch().finally(final, notify);
and here is my function definitions.
var errorHandler = function(err) {
console.error('Error returned from function:', err);
};
var final = function() {
console.log('Called Finally');
};
var notify = function(notification) {
console.log('Notify', notification);
};
var success = function(data) {
console.log('Success data');
console.log(data);
};
Can we get each promise function notification or this is not feasible?
But Now my query changed to
How do we add a .notify for the $q.all() ?
as I understand that $q.all returns a single promise which contains all promise resolve data;

Why won't promise resolve in a service?

I have a factory NewEditSvc where I'm trying to share functionality common to NewCtrl and EditCtrl. One thing I'm trying to do is get feeParameters from the server inside the service but this is not working.
angular.module('feeSuitesApp')
.factory('NewEditSvc', function($q, FeeTypesSvc){
var _feeParameters = {};
function getParameters(){
return _feeParameters;
}
function setParameters(feeType){
var promise = $q(function(resolve, reject){
FeeTypesSvc.resource.get({id: feeType.id, association: 'fee_parameters'}).
$promise.then(function(data){
resolve(data);
}, function(error){
reject(error);
})
});
promise.then(function(data){
_feeParameters = data.master;
});
}
return {
getParameters: getParameters,
setParameters: setParameters
}
});
angular.module('feeSuitesApp')
.controller('NewCtrl', function(NewEditSvc, feeType){
$scope.feeParameters = NewEditSvc.getParameters();
$scope.feeType = feeType;
$scope.setParameters = function(feeType){
NewEditSvc.setParameters(feeType);
};
$scope.setParameters($scope.feeType);
});
After my last line $scope.feeParameters is not set. But if I return the promise variable from NewEditSvc.setParameters() and then in my NewCtrl I do the following:
var promise = $scope.setParameters($scope.feeType);
promise.then(function(data){
$scope.feeParameters = data.master;
});
Then everything works. What am I doing wrong?
The problem is, that you're not returning your promise in setParameters. https://docs.angularjs.org/api/ng/service/$q/
Just add return promise.

Catching errors at the end of a chain of promises

I am using a factory to query data from Parse.com, this occurs many times in my ionic app and would like to apply a little more DRY to my code.
To call data I am using:
ParseFactory.provider('Clients', query).getAll().success(function(data) {
$localStorage.Clients = data.results;
}).error(function(response) {
errorFactory.checkError(response);
});
And often run many of these back to back to get data from different classes on the loading of a page.
Is it possible to use one error block at the end of all of these? like this:
ParseFactory.provider('Favourites', query).getAll().success(function(data) {
$localStorage.Favourites = data.results;
})
ParseFactory.provider('Somethings/', query).getAll().success(function(data) {
$localStorage.Programmes = data.results;
})
ParseFactory.provider('UserItems', query).getAll().success(function(data) {
$localStorage.UserExercises = data.results;
})
ParseFactory.provider('Customers', query).getAll().success(function(data) {
$localStorage.Clients = data.results;
}).error(function(response) {
errorFactory.checkError(response);
});
You could create helper method:
function query(resource, query) {
function querySucceeded(data) {
$localStorage[resource] = data.results;
}
function queryFailed() {}
ParseFactory.provider(resource, query)
.getAll()
.success(querySucceeded)
.error(queryFailed);
}
and, then just call:
query('Favourites', query);
query('Customers', query);
and so on.
Alternatively, you could factor queryFailed out, as such:
function query(resource, query) {
function querySucceeded(data) {
$localStorage[resource] = data.results;
}
return ParseFactory.provider(resource, query)
.getAll()
.success(querySucceeded);
}
function queryFailed() {
}
$q.all([
query('Favourites', query1),
query('UserItems', query2)])
.error(queryFailed);
$q.all takes an array (or object) of promises, and returns a single one.
The returned promise is resolved when all the original promises are resolved. It's rejected as soon as one of the original promises is rejected.
So, what you can simply do is something like
var request = function(path) {
return ParseFactory.provider(path, query).getAll();
};
var promises = {
favourites: request('Favourites'),
programmes: request('Somethings/'),
exercises: request('UserItems'),
customers: request('Customers')
};
$q.all(promises).then(function(results) {
$localStorage.Favourites = results.favourites.data.results;
// ...
}).catch(function() {
// ...
});

Chaining API calls with $q.all

I don't know
how to add the returned data from resource to the promise array correctly. When I log it to the console its empty.
Here is my code:
var d = $q.defer();
var promises = [];
_.each(recipe.credentials, function(credential) {
APIService.save({route:'credential'},credential).$promise.then(function(data) {
promises.push(data)
});
});
$q.all(promises).then(function(data) {
console.log(data);
d.resolve();
});
return d.promise;
Updated Code:
var d = $q.defer();
var promises = recipe.credentials.map(function(credential) {
return APIService.save({route:'credential'},credential).$promise;
});
return $q.all(promises)
You should wrap promises when they're created, and don't forget the .catch handler:
$q.all(recipe.credentials.map(function(credential) {
return APIService.save({route:'credential'},credential).$promise;
})).then(function(data) {
console.log(data);
}).catch(function(reason) {
console.log(reason);
});
Also, most probably there's no need to create another defer - just return the result of $q.all into outer world.
P.S. I highly recommend reading this article about promises and their usage. )

AngularJS: $q wait for all even when 1 rejected

I've been trying to wait for a couple of promises with Angular's $q but there seems to be no option to 'wait for all even when a promis is rejected'.
I've created an example (http://jsfiddle.net/Zenuka/pHEf9/21/) and I want a function to be executed when all promises are resolved/rejected, is that possible?
Something like:
$q.whenAllComplete(promises, function() {....})
EDIT: In the example you see that the second service fails and immediately after that the function in $q.all().then(..., function(){...}) is being executed. I want to wait for the fifth promise to be completed.
Ok, I've implemeted a basic version myself (I only want to wait for an array of promises). Anyone can extend this or create a cleaner version if they want to :-)
Check the jsfiddle to see it in action: http://jsfiddle.net/Zenuka/pHEf9/
angular.module('test').config(['$provide', function ($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
// Extention for q
$q.allSettled = $q.allSettled || function (promises) {
var deferred = $q.defer();
if (angular.isArray(promises)) {
var states = [];
var results = [];
var didAPromiseFail = false;
if (promises.length === 0) {
deferred.resolve(results);
return deferred.promise;
}
// First create an array for all promises with their state
angular.forEach(promises, function (promise, key) {
states[key] = false;
});
// Helper to check if all states are finished
var checkStates = function (states, results, deferred, failed) {
var allFinished = true;
angular.forEach(states, function (state, key) {
if (!state) {
allFinished = false;
}
});
if (allFinished) {
if (failed) {
deferred.reject(results);
} else {
deferred.resolve(results);
}
}
}
// Loop through the promises
// a second loop to be sure that checkStates is called when all states are set to false first
angular.forEach(promises, function (promise, key) {
$q.when(promise).then(function (result) {
states[key] = true;
results[key] = result;
checkStates(states, results, deferred, didAPromiseFail);
}, function (reason) {
states[key] = true;
results[key] = reason;
didAPromiseFail = true;
checkStates(states, results, deferred, didAPromiseFail);
});
});
} else {
throw 'allSettled can only handle an array of promises (for now)';
}
return deferred.promise;
};
return $q;
}]);
}]);
Analogous to how all() returns an array/hash of the resolved values, the allSettled() function from Kris Kowal's Q returns a collection of objects that look either like:
{ state: 'fulfilled', value: <resolved value> }
or:
{ state: 'rejected', reason: <rejection error> }
As this behavior is rather handy, I've ported the function to Angular.js's $q:
angular.module('your-module').config(['$provide', function ($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
$q.allSettled = $q.allSettled || function allSettled(promises) {
// Implementation of allSettled function from Kris Kowal's Q:
// https://github.com/kriskowal/q/wiki/API-Reference#promiseallsettled
var wrapped = angular.isArray(promises) ? [] : {};
angular.forEach(promises, function(promise, key) {
if (!wrapped.hasOwnProperty(key)) {
wrapped[key] = wrap(promise);
}
});
return $q.all(wrapped);
function wrap(promise) {
return $q.when(promise)
.then(function (value) {
return { state: 'fulfilled', value: value };
}, function (reason) {
return { state: 'rejected', reason: reason };
});
}
};
return $q;
}]);
}]);
Credit goes to:
Zenuka for the decorator code
Benjamin Gruenbaum for pointing me in the right direction
The all implementation from Angular.js source
The promise API in angularJS is based on https://github.com/kriskowal/q. I looked at API that Q provides and it had a method allSettled, but this method has not been exposed over the port that AngularJS uses. This is form the documentation
The all function returns a promise for an array of values. When this
promise is fulfilled, the array contains the fulfillment values of the
original promises, in the same order as those promises. If one of the
given promises is rejected, the returned promise is immediately
rejected, not waiting for the rest of the batch. If you want to wait
for all of the promises to either be fulfilled or rejected, you can
use allSettled.
I solved this same issue recently. This was the problem:
I had an array of promises to handle, promises
I wanted to get all the results, resolve or reject
I wanted the promises to run in parallel
This was how I solved the problem:
promises = promises.map(
promise => promise.catch(() => null)
);
$q.all(promises, results => {
// code to handle results
});
It's not a general fix, but it is simple and and easy to follow. Of course if any of your promises could resolve to null then you can't distinguish between that a rejection, but it works in many cases and you can always modify the catch function to work with the particular problem you're solving.
Thanks for the inspiration Zenuka, you can find my version at https://gist.github.com/JGarrido/8100714
Here it is, in it's current state:
.config( function($provide) {
$provide.decorator("$q", ["$delegate", function($delegate) {
var $q = $delegate;
$q.allComplete = function(promises) {
if(!angular.isArray(promises)) {
throw Error("$q.allComplete only accepts an array.");
}
var deferred = $q.defer();
var passed = 0;
var failed = 0;
var responses = [];
angular.forEach(promises, function(promise, index) {
promise
.then( function(result) {
console.info('done', result);
passed++;
responses.push(result);
})
.catch( function(result) {
console.error('err', result);
failed++;
responses.push(result);
})
.finally( function() {
if((passed + failed) == promises.length) {
console.log("COMPLETE: " + "passed = " + passed + ", failed = " + failed);
if(failed > 0) {
deferred.reject(responses);
} else {
deferred.resolve(responses);
}
}
})
;
});
return deferred.promise;
};
return $q;
}]);
})
A simpler approach to solving this problem.
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
$q.allSettled = $q.allSettled || function (promises) {
var toSettle = [];
if (angular.isArray(promises)) {
angular.forEach(promises, function (promise, key) {
var dfd = $q.defer();
promise.then(dfd.resolve, dfd.resolve);
toSettle.push(dfd.promise);
});
}
return $q.all(toSettle);
};
return $q;
}]);
A simple solution would be to use catch() to handle any errors and stop rejections from propagating. You could do this by either not returning a value from catch() or by resolving using the error response and then handling errors in all(). This way $q.all() will always be executed. I've updated the fiddle with a very simple example: http://jsfiddle.net/pHEf9/125/
...
function handleError(response) {
console.log('Handle error');
}
// Create 5 promises
var promises = [];
var names = [];
for (var i = 1; i <= 5; i++) {
var willSucceed = true;
if (i == 2) willSucceed = false;
promises.push(
createPromise('Promise' + i, i, willSucceed).catch(handleError));
}
...
Be aware that if you don't return a value from within catch(), the array of resolved promises passed to all() will contain undefined for those errored elements.
just use finally
$q.all(tasks).finally(function() {
// do stuff
});

Resources