I'm really new to AngularJS, and actually relatively new to programming altogether. Basically, I want to request the JSON api from Jenkins, for the list of running jobs in 2 different folders. Inside that data, there is a url to each individual job, which I want to get the data for, as well. So, I need to do another $http.get request for each job, passing the url (which is a value inside the data of the first request) as the parameters.
Initially, I had one request inside another, inside a couple loops to iterate between
The folders, and in each folder
The jobs.
After doing some research, I realized that due to $http requests being async, and for loops being sync, that method was not going to work. So I have a service, which, using $q, collects the promise of the first request, but I don't know how to use the data from the first request as a parameter for the second request. Can someone help, please?
So assume you have two calls, a and b. They both return a list of Jenkins jobs. You can group these using an array of promises and then using $q.all(promises) to group these responses:
var jenkinsPromises = [];
// You could loop over this part if you have an array of calls for example.
jenkinsPromises.push($http.get{ // call folder one });
jenkinsPromises.push($http.get{ // call folder two });
// Now wait for all calls to finish and iterate over their responses.
$q.all(jenkinsPromises).then(function (jenkinsResponses) {
// jenkinsResponses is an array with objects
// each object being a response from Jenkins
}
In the above example, jenkinsResponses, you will find a combined result of the, lets say 'first layer' calls to Jenkins. This array contains response-objects which, as you say, contain the urls you need to make calls to.
Using the same practise as above, you can then group the calls in a promise array. Then we use $q.all() again to group their responses.
$q.all(jenkinsPromises).then(function (jenkinsResponses) {
var i, j, current, urlPromises = [];
for (i = 0, j = jenkinsResponses.length; i < j; i++) {
current = jenkinsResponses[i];
// Push the call to the urlPromises array.
// Note that this is a dummy call.
urlPromises.push($http.get{ current.url] };
}
$q.all(urlPromises).then(function (urlResponses) {
// urlResponses contains a result of all calls to the urls.
}
}
You should use async waterfall https://github.com/caolan/async for async request.
Example:
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});
If I'm understanding right, this is the same thing I had to learn in the last few months. Basically, this:
requestFunction(...)
.then(function(response)
{
nextRequest(...).then(...);
}
);
The catch and finally methods are often implemented for this sort of control flow, which is called promises. It's worth looking into; I personally can't stand AngularJS, but understanding it is important to my day job, and this is pretty fundamental to it.
If I unterstand you need to loop twice. So, in your first loop you call your first request. And in the callback of this request, you can put the code for your second loop.
e.g:
for(var i=0; i<cpt1; cpt1++){
request1.then(function(response1){
for(var j=0; j<cpt2; cpt2++){
request2.then(function(response2){
});
}
});
}
Related
In my AngularJS application I have an array of parameters (some IDs, for example), which should be used as a parameters for an ajax call queue. The problem is that array might contain more than 600 items and if I just recursively make the ajax call using the forEach loop, the browser page eventually stops responding before each of the requests are resolved, since each of responses updates the view. Is there a technique, which could allow to send ajax requests, for example, by 5 requests at a time asynchronously, and only when those are finished, proceed the next 5?
I think best solution would be to change the endpoint to allow an array of Id's, but I guess that is not an option. You can use promises to limit the number of simultaneous requests:
function chunkedAjax(idArray, index, limit) {
if(index >= idArray.length) {
return;
}
var index = index || 0;
var limit = limit || 5;
var chunk = idArray.slice(index, limit);
var promises = [];
angular.forEach(chunk, function(id) {
var deferred = $q.defer();
promises.push(deferred.promise);
makeAjaxCall(id).then(function(response){
deferred.resolve();
});
});
$q.all(promises).then(function() {
chukedAjax(idArray, index + limit, limit);
});
}
This is a recursive function, so be warned. I would debug this heavily before putting into production.
You will also likely need to modify your makeAjaxCall function to return a promise if it does not already, or pass the promise object to it so that it can be resolved when the ajax call completes
Take a look at $q.all(). This lets you execute a callback if multiple requests have finished. And therefore you are able to recursively execute a limited number of requests until all items are processed.
Yes!
Take a look on this module: https://github.com/caolan/async
lets suppose I have the following:
var myurls = ['http://server1.com', 'http://server2.com', 'http:sever2.com', etc ]
Each url is a "fallback" and should be used only if the previous one cannot be reached. In other words, this list specifies a priority. Lets also assume that this list can be of any length - I don't know and must iterate.
How do I go about writing a function, lets say "reachability" that loops through this array and returns the first reachable server?
I can't do $http.all as it is parallel. I can't run a while loop with an $http.get inside because the result may come later and in the mean time, my UI will freeze.
Please note I am not using jQuery. I am using ionic, which has a version of jQuery-lite in it.
Various examples I've seen talk about chaining them in .then, which is fine if you know the # of URLs before hand, but I don't.
thanks
Just reduce over the array:
myurls.reduce((p, url) => p.catch(() => http.get(url).then(() => url)),
Promise.reject());
Flow explained:
It's based off the perhaps more common pattern of using reduce to build a promise chain, like so: [func1, func2].reduce((p, f) => p.then(f), Promise.resolve()); is equivalent to Promise.resolve().then(func1).then(func2) (the last arg of reduce is the initial value).
In your case, since you're retrying on failure, you want to build a retry (or reject) chain, so we must start with Promise.reject() instead. Promise.reject().catch(func1).catch(func2)
I guess recursion and chaining could suit your needs:
var findFirstReachableUrl = function (urls) {
if (urls.length > 0) {
return $http.get(urls[0]).then(function () {
return urls[0];
}, function () {
return findFirstReachableUrl(urls.slice(1));
});
} else {
return $q.reject("No reachable URL");
}
}
Call:
findFirstReachableUrl(myurls).then(function (firstReachableUrl) {
// OK: do something with firstReachableUrl
}, function () {
// KO: no url could be reached
});
I'd like to call multiple times the same API but with different keys to get results faster.
The thing is I need to not wait to receive the result from the first call to start the second call, etc...
The steps are :
1) I have an array with all the different keys.
2) This gets data from the API ("APIKeys" is the array that contains all the keys) :
_.map(APIKeys,function(value, index){
var newCount = count+(25*index);
parseResult(Meteor.http.get("http://my.api.com/content/search/scidir?query=a&count=25&start="+newCount+"&apiKey="+value+""));
});
3) I call a function (named "parseResult") that will format and filter the result I get from the API and save it into the database.
I want to call the function (step 3) without having to wait that I get the data from the API and continue with the other keys while the request is being made.
Do you know how I could do that with meteor ?
Thanks
Do something like this to use HTTP.get() in an async manner:
HTTP.get("http://my.api.com/content/search/scidir?query=a&count=25&start="+newCount+"&apiKey="+value+"", function (error, result) {
// parse the result here
});
And see the docs here:
http://docs.meteor.com/#/full/http_get
I have an Angular.js application and I need to call three different resources and once they all complete, use them together. My three calls are below.
# Get the curriculum
$scope.curriculum = CurriculumResource.get id: $routeParams.id
# Get the list of courses
$scope.courses = CourseResource.query()
# Get the list of groups
$scope.groups = GroupResource.query()
How can I perform more logic once I know the requests are all completed. I've tried using $watchGroup shown below and $watchCollection but neither seem to be working.
$scope.$watchGroup ['curriculum', 'courses', 'groups'], ->
# Shouldn't this run each time something in the above array changes?
console.log 'Only runs once'
# The values of the items in the below if statement eventually give a true result
# but the if statement never runs when they are true
if $scope.curriculum.groups and $scope.groups.length
console.log 'never gets here!'
I believe you could accomplish this with $q.all assuming all of your requests return promises. Something like
$q.all([CurriculumResource.get({id: $routeParams.id}), CourseResource.query(), GroupResource.query()])
.then(function(results){
// results will be an array of values from resolved promises in the original order
})
.catch(function(err) {
// will fail on any rejected promise
});
Inject $q service, and use it this way:
$q.all(
[
CourseResource.query(),
CurriculumResource.query(),
GroupResource.query()
]
).then(function(response) {
$scope.courses = response[0];
$scope.curriculum = response[1];
$scope.groups = response[2];
// do what you need to do when all data is available
});
Also, you need to make sure your services are returning $q deferreds, all request returned by $http are deferred so you can return them, but maybe you want to wrap it to process the results (for example to extract the important info, o to preprocess data with model logic):
...
query: function() {
return $http.get("...url...").then(function(response) {
//process response here before return it
}); // of course, the use of .then is optional
}
...
I'm trying to implement the folowing scenario, using JQuery deferred, without much luck.
What parts of the deferred api would you use, and how would you structure your calls to achieve the following:
1st ajax callA to serviceA retrieve a list of Ids
wait until this call returns
then n ajax calls to serviceB, each call using a using an Id from the list returned by callA
wait until all serviceB calls have returned
then a final ajax call to serviceC
You could do like this (more or less pseudocode):
(function() {
// new scope
var data = []; // the ids coming back from serviceA
var deferredA = callToServiceA(data); // has to add the ids to data
deferredA.done(function() { // if callToServiceA successful...
var deferredBs = [];
for i in data {
deferredBs.push(callToServiceB(...));
}
$.when.apply($, deferredBs).then(callToServiceC);
});
}());
The callToServiceX function should return the promise object returned by $.ajax.
There might be a "cleaner" solution than having data in a shared scope, with resolve, but the setup would be a bit more difficult (and not necessarily more readable).