Chaining API calls with $q.all - angularjs

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. )

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;

Chaining promises in Angular with $q.all

I have the following layout for a promise chain in a service...
this.getData = function(params) {
var promise = $http({}).then(function(firstdata) {
// work on the first data and then call a number of promises for fetch additional data
var promises = list.map(function(item) {
return $http({}).then(function(result2) {
// this is an amalgamation of all the data from the various calls
return finalData;
});
return $q.all(promises);
})
});
return promise;
}
Then in my controller i'm doing
myService.getData().then(function(data){
});
The issue lies in the fact that THEN in the controller executes before PROMISES (note the plural) has returned a value.
This is likely something silly but any thoughts on how to simplify this/make it work would be handy!
Currently your inner promises($q.all promise) isn't returned from promise variable. You should also return promises(plural), to make sure chain should work.
this.getData = function(params) {
var promise = $http({}).then(function(firstdata) {
// creating new promise array in `promises` function
var promises = list.map(function(item) {
return $http({}).then(function(result2) {
// this is an amalgamation of all the data from the various calls
return finalData;
});
return $q.all(promises);
});
return promises; //returning inner promise
});
return promise;
}

Foreach into foreach with $http promise

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

angular $q, How to chain multiple promises within and after a for-loop

I want to have a for-loop which calls async functions each iteration.
After the for-loop I want to execute another code block, but not before all the previous calls in the for-loop have been resolved.
My problem at the moment is, that either the code-block after the for-loop is executed before all async calls have finished OR it is not executed at all.
The code part with the FOR-loop and the code block after it (for complete code, please see fiddle):
[..]
function outerFunction($q, $scope) {
var defer = $q.defer();
readSome($q,$scope).then(function() {
var promise = writeSome($q, $scope.testArray[0])
for (var i=1; i < $scope.testArray.length; i++) {
promise = promise.then(
angular.bind(null, writeSome, $q, $scope.testArray[i])
);
}
// this must not be called before all calls in for-loop have finished
promise = promise.then(function() {
return writeSome($q, "finish").then(function() {
console.log("resolve");
// resolving here after everything has been done, yey!
defer.resolve();
});
});
});
return defer.promise;
}
I've created a jsFiddle which can be found here http://jsfiddle.net/riemersebastian/B43u6/3/.
At the moment it looks like the execution order is fine (see the console output).
My guess is, that this is simply because every function call returns immediately without doing any real work. I have tried to delay the defer.resolve with setTimeout but failed (i.e. the last code block was never executed). You can see it in the outcommented block in the fiddle.
When I use the real functions which write to file and read from file, the last code block is executed before the last write operation finishes, which is not what I want.
Of course, the error could be in one of those read/write functions, but I would like to verify that there is nothing wrong with the code I have posted here.
What you need to use is $q.all which combines a number of promises into one which is only resolved when all the promises are resolved.
In your case you could do something like:
function outerFunction() {
var defer = $q.defer();
var promises = [];
function lastTask(){
writeSome('finish').then( function(){
defer.resolve();
});
}
angular.forEach( $scope.testArray, function(value){
promises.push(writeSome(value));
});
$q.all(promises).then(lastTask);
return defer.promise;
}
With the new ES7 you can have the same result in a much more straightforward way:
let promises = angular.forEach( $scope.testArray, function(value){
writeSome(value);
});
let results = await Promise.all(promises);
console.log(results);
You can use $q and 'reduce' together, to chain the promises.
function setAutoJoin() {
var deferred = $q.defer(), data;
var array = _.map(data, function(g){
return g.id;
});
function waitTillAllCalls(arr) {
return arr.reduce(function(deferred, email) {
return somePromisingFnWhichReturnsDeferredPromise(email);
}, deferred.resolve('done'));
}
waitTillAllCalls(array);
return deferred.promise;
}
This worked for me using the ES5 syntax
function outerFunction(bookings) {
var allDeferred = $q.defer();
var promises = [];
lodash.map(bookings, function(booking) {
var deferred = $q.defer();
var query = {
_id: booking.product[0].id,
populate: true
}
Stamplay.Object("product").get(query)
.then(function(res) {
booking.product[0] = res.data[0];
deferred.resolve(booking)
})
.catch(function(err) {
console.error(err);
deferred.reject(err);
});
promises.push(deferred.promise);
});
$q.all(promises)
.then(function(results) { allDeferred.resolve(results) })
.catch(function(err) { allDeferred.reject(results) });
return allDeferred.promise;
}

AngularJS : chaining http promises $q in a service

i have problems when it comes to $http promises in angularjs. i am doing this in my service: (the getSomething function should chain two promises)
the second function uses a external callback function!
app.service('blubb', function($http, $q) {
var self = this;
this.getSomething = function(uri, data) {
return self.getData(uri).then(function(data2) {
return self.compactData(uri, data2);
});
};
this.getData = function(uri) {
var deferred = $q.defer();
$http.get(uri).success(function(data) {
deferred.resolve(data);
}).error(function() {
deferred.reject();
});
return deferred.promise;
};
this.compactData = function(uri, data) {
var deferred = $q.defer();
/* callback function */
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
console.log(err);
deferred.reject(err);
}
/* end of function */
return deferred.promise;
};
});
when i use the service in my controller it doesn't output the console.log:
blubb.getSomething(uri, input).then(function(data) {
console.log(data)
});
edit:
if i define the callback function by myself in 'compactData' it works, but i am using "jsonld.compact" from https://raw.github.com/digitalbazaar/jsonld.js/master/js/jsonld.js and THIS doesn't work!
jsonld.compact(input, context, function(err, compacted) {
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
deferred.reject('JSON-LD compacting');
}
});
i am getting the console.log output in jsonld.compact but the resolve doesn't work and i don't know why..
it only works with $rootScope.$apply(deferred.resolve(compacted));
I'm using chaining promises like this:
$http.get('urlToGo')
.then(function(result1) {
console.log(result1.data);
return $http.get('urlToGo');
}).then(function(result2) {
console.log(result2.data);
return $http.get('urlToGo');
}).then(function(result3) {
console.log(result3.data);
});
Chaining promises works here : jsfiddle
In your implementation, if $http.get or compactData goes wrong your console.log(data) will not be call.
You should maybe catch errors :
blubb.getSomething(uri, input).then(function(data) {
console.log(data);
}, function(err) {
console.log("err: " + err);
});
Whenever you use an external (external to AngularJS) callback that runs in a new turn/tick, you have to call $apply() on the appropriate scope after it has been invoked. This lets AngularJS know it has to update. You'll probably want to make sure you're only calling it once -- after all of the promises have been resolved. As an aside, jsonld.js provides a promises/future API, so if you're already using promises, you don't have to do that wrapper code above. Instead you can do:
var promisesApi = jsonld.promises();
var promise = promisesApi.compact(input, context);
// do something with the promise
I would suggest you to use a Factory instead of a service.
Just return the function from the factory and use it in your controller

Resources