Return multiple data independent resource promises - angularjs

Via this link I've found an example of returning multiple resource promises (lines 44-52):
http://embed.plnkr.co/LZad4ZyEYjrbKiTPbAwu/script.js
var GistsData = Gists.query();
var MetaData = Meta.get();
GistsData.$promise.then(function(response) {console.log('Resource 1 data loaded!')});
MetaData.$promise.then(function(response) {console.log('Resource 2 data loaded!')});
return $q.all([GistsData.$promise, MetaData.$promise]);
In my case the second resource API call (MetaData) is dependent on a specific value that is returned by the first resource API call (GistsData).
I try to figure out how I can use a value that is returned by GistData (for example an ID) in the MetaData resource? Like this:
var MetaData = Meta.get({ id : GistsData.id });
I want to return a promise after the MetaData with the ID has returned a promise.
Thank you

Firstly I suggest you do a little more reading about promises, as they are awesome :)
As for your question, what you want to do is promise chaining. Notice how you are utilising the .then() functions for each of the resource promises. then() gets called once the promise has resolved, which in your case is when the queries have returned.
So instead of running each one independently, use the then() function of the first promise to then begin running the second. For example:
return Gists.query().$promise.then(function(response){
// Gists has finished and the data it returned is in response
// Now run your second query, using the response from the first
return Meta.get({ id : response.id }).$promise.then(function(nextResponse){
// Now nextResponse will contain the result of Meta.get(), having used the id that returned from the first response
console.log(nextResponse);
});
});
Now there are nicer ways to write the above, but hopefully it explains chaining enough for you.

Related

Post promise returns ids that are needed for another post promise

I am using http post methods to get the data from an endpoint to populate a table. I first need to get the id of the recipients, and subsequently I need to use the Id of these recipients to grab an object belonging to the recipient from another endpoint.
I have a http post that returns a promise. Using .then, I store these id numbers which I store on a javascript array. I then need to make one http post for each id on the array that will return another promise. Not sure if this is the best way to go:
getRecipients = httpService.doPost("/search", [], {userId:"test"});
getRecipients.then(function(recipientData){
vm.recipients = recipientData.data.recipients;
}).then(function(){
for(x in vm.recipients)
{
httpService.doPost("/searchObjects", [], vm.recipients[x].id)
.then(function(){
//store each object returned on another array here....
});
}
});
NOTE: doPost(endpoint, not used, parameters to do the search)
With the above method, the problem is that the for loop will not wait for the then and will move to the next iteration once the doPost has been called.
I guess I can use bluebird, but not sure if that would be the best way to go in here, and if so, how should it be done (note that this is server side javascript so require is not available per say unless I use require.js)?
If I'm understanding you correctly, you're looking for something like this:
getRecipients = httpService.doPost("/search", [], {userId:"test"});
getRecipients.then(function(recipientData){
vm.recipients = recipientData.data.recipients;
var promises = [];
for (x in vm.recipients) {
promises.push(httpService.doPost("/searchObjects", [], vm.recipients[x].id));
}
// in this case $q.all waits for all of the requests to finish
// then gives the responses
$q.all(promises).then(function(responses) {
// responses is an array of the responses
// from each request in the promise array
});
});

angularjs chain http post sequentially

In my application, I am storing data in local storage and trigger async http post in the background. Once successfully posted, the posted data gets removed from local storage. When http post is in process, there may be more data added to local storage so I need to queue up the post and sequentially process it because, I need to wait for the local storage to be cleared from the successful posts. The task should be called recursively until there is data in local storage.
taskQueue: function () {
var deferred = $q.defer();
queue.push(deferred);
var promise = deferred.promise;
if (!saveInProgress) {
// get post data from storage
var promises = queue.map(function(){$http.post(<post url>, <post data>).then(function(result){
// clear successful data
deferred.resolve(result);
}, function(error){
deferred.reject(error);
})
})
return $q.all(promises);
}
As angular newbie, I am having problems with the above code not being sequential. How can I achieve what I intend to? The queue length is unknown and also the queue length increases as the process is in progress. Please point me to other answers if this is a duplicate.
Async.js sounds a good idea but if you don't want to use a library ...
$q.all will batch up an array of requests and fire them at the same time and will resolve when ALL promises in the array resolve - WHICH IS NOT WHAT YOU WANT.
to make $http calls SEQUENTIALLY from an array do this ....
var request0 = $http.get('/my/path0');
var request1 = $http.post('/my/path1', {data:'fred'});
var request2 = $http.get('/my/path2');
var requestArray = [];
then ...
requestArray.push(request0);
requestArray.push(request1);
requestArray.push(request2);
then ...
requestArray[0].then(function(response0) {
// do something with response0
return requestArray[1];
}).then(function(response1) {
// do something with response1
return requestArray[2];
}).then(function(response2) {
// do something with response2
}).catch(function(failedResponse) {
console.log("i will be displayed when a request fails (if ever)", failedResponse)
});
While having a library solution would be great (per #nstoitsev's answer), you can do this without it.
sequential requests of unknown length
Just to recap:
we do not know the number of requests
each response may enqueue another request
A few assumptions:
all requests will be working on a common data object (local storage in your case)
all requests are promises
running the queue
function postMyData (data){
return $http.post(<url>, data)
}
var rqsts = []
function executeQueue (){
if(!rqsts.length)
//we're done
return
var rqst = rqsts.shift()
rqst()
.then(function(rsp){
//based on how you're determining if you need to do another request...
if(keepGoing)
rqsts.push(postMyData(<more data>))
})
}
codepen - http://codepen.io/jusopi/pen/VaYRXR?editors=1010
I intentionally left this vague because I don't understand what the conditions for failure are and if you wanted to vary up the requests to use more than the same $http.post call, you could pass it back in some way.
and just a suggestion
As angular newbie...
Many things are progressing towards this whole functional, reactive programming paradigm. Since you're relatively new to Angular and NG2 already has some of this built in, it might be worthy of your attention. I think rxjs is already in many NG2 example bundles.
The easies way to achieve this is by using Async.js. There you can find a method called mapSeries. You can run it over the queue and it will sequentially process all elements of the array one by one, and will continue to the next element only when the correct callback is called.

How to loop through $resource returned query and get desired value?

I am using MEANJS
In my controller i have
// Find a list of Cars
$scope.findHome = function() {
$scope.cars = Cars.query();
console.log($scope.cars);
};
Which outputs
here i want to get the _id string inside the first array 0: Resource
I tried $scope.cars[0]._id which returns undefined, Please help.
You are inspecting the results of the query immediately after the call, but ngResource is asynchronous, so perhaps the data has not yet returned from the server by the time you are trying to access it. Try putting your access in the callback function passed to query().
$scope.cars = Cars.query(function() {
console.log($scope.cars);
console.log($scope.cars[0]._id);
});

Non-essential Angular promises

I'm trying to rewrite the code for http://m.amsterdamfoodie.nl in a more modern style. Basically single page Angular app downloads a set of restaurants with locations and places them on a map. If the user is the Amsterdam area then the user's location is added too, as are the distances to places.
At present I manage the asynchronous returns using a lot of if (relevant object from other async call exists) then do next step. I'd like to make more use of promises would be better.
So, flow control should be:
Start ajax data download, and geolocation call
if geolocation returns first, store coords for later
once ajax data is downloaded
if geolocation available
calculate distances to each restaurant, and pass control to rendering code
else pass control immediately to render code
if geolocation resolves later, calculate distances and re-render
The patterns I find on the internet assume that all async calls must return successfully before continuing, whereas my geolocation call can fail (or return a location far from amsterdam) and that's OK. Is there a trick I could use in this scenario or are the conditional statements really the way to go?
Every time you use .then, you essentially create a new promise based on the previous promise and its state. You can use that to your advantage (and you should).
You can do something along the lines of:
function getGeolocation() {
return $http.get('/someurl').then(
function resolveHandler(response) {
// $http.X resolves with a HTTP response object.
// The JSON data is on its `data` attribute
var data = response.data;
// Check if the data is valid (with some other function).
// By this, I mean e.g. checking if it is "far from amsterdam",
// as you have described that as a possible error case
if(isValid(data)) {
return data;
}
else {
return null;
}
},
function rejectHandler() {
// Handle the retrieval failure by explicitly returning a value
// from the rejection handler. Null is arbitrarily chosen here because it
// is a falsy value. See the last code snippet for the use of this
return null;
}
);
}
function getData() {
return $http.get('/dataurl').then(...);
}
and then use $q.all on both promises, which in turn creates a new promise that resolves as soon as all the given promises have resolved.
Note: In Kris Kowal's Q, which Angular's $q service is based on, you could use the allSettled method, which does almost the same as all, but resolves when all promises are settled (fulfilled or rejected), and not only if all promises are fulfilled. Angular's $q does not provide this method, so you can instead work your way around this by explicitly making the failed http request resolve anyways.
So, then you can do something like:
$q.all([getData(), getGeolocation()])
.then(function(data, geolocation) {
// `data` is the value that getData() resolved with,
// `geolocation` is the value that getGeolocation() resolved with.
// Check the documentation on `$q.all` for this.
if(geolocation) {
// Yay, the geolocation data is available and valid, do something
}
// Handle the rest of the data
});
Maybe I'm missing something... but since you have no dependencies between the two async calls, I don't see why you can't just follow the logic you outlined:
var geoCoordinates = null;
var restaurants = null;
var distances = null;
getRestaurantData()
.then(function(data){
restaurants = data;
if (geoCoordinates) {
distances = calculate(restaurants, geoCoordinates);
}
// set $scope variables as needed
});
getGeoLocation()
.then(function(data){
geoCoordinates = data;
if (restaurants){
distances = calculate(restaurants, geoCoordinates)
}
// set $scope variables as needed
});

TVRage consume service via AngularJS

i am trying to consume this webservice (http://services.tvrage.com/feeds/show_list.php) from TVRage using Angularjs.
I can 'connect' to the service (using firebug I see GET show_list.php STATUS 200 OK) but when i try to print any data from the response I get none.
This is the code that i use:
var TV_Episodes = angular.module('TV_Episodes', ['ngResource']);
TV_Episodes.controller('GetAllEpisodes', function($scope, $resource) {
var dataService = $resource('http://services.tvrage.com/feeds/show_list.php');
$scope.data = dataService.get();
console.log($scope.data());
});
any ideas on how I can just console.log the the response?
UPDATE 1:
After some more trying i found out that that i get the following error as a response from TVRAGE.
"XMLHttpRequest cannot load http://services.tvrage.com/feeds/show_list.php. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access."
therefor i tweaked my code so
var dataService = $resource('http://services.tvrage.com/feeds/show_list.php?key=xxxx',{},{headers: { 'Access-Control-Allow-Origin': '*' }});
but i still get the same error as before.
$resource.get() returns a promise, which means you are likely printing to the console prior to the data being retrieved. Instead use the appropriate callback function:
$scope.data = dataService.get(function() { console.log($scope.data); });
The get method is asyncronous. When it is called it returns immediately with a reference to an object (or array, if specified - but not a promise as indicated in MWay's answer). Then, later, that same reference is updated with the data that is returned from the server on success. Here's the relevant part from the documentation:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most cases one never has to write a callback function for the action methods.
As fast as the request might be, it won't resolve until the event loop comes around again. The resource is helpfully designed to free you up from having to worry about writing callbacks. If you need to though, the get method takes callback function parameters that will be invoked when the request resolves and the data is ready.
var TV_Episodes = angular.module('TV_Episodes', ['ngResource']);
TV_Episodes.controller('GetAllEpisodes', function($scope, $resource) {
var dataService = $resource('http://services.tvrage.com/feeds/show_list.php');
$scope.data = dataService.get(function () {
console.log($scope.data());
});
});
Or, you can access the promise used for processing the request by using *$promise", which is a property on empty instance object returned from get.

Resources