Wait for all $http requests to complete in Angular JS - javascript

I have a page than can make a different number of $http requests depending on the length of a variables, and then I want to send the data to the scope only when all the requests are finished. For this project I do not want to use jQuery, so please do not include jQuery in your answer. At the moment, the data is sent to the scope as each of the requests finish, which isn't what I want to happen.
Here is part of the code I have so far.
for (var a = 0; a < subs.length; a++) {
$http.get(url).success(function (data) {
for (var i = 0; i < data.children.length; i++) {
rData[data.children.name] = data.children.age;
}
});
}
Here is the part that I am sceptical about, because something needs to be an argument for $q.all(), but it is not mentioned on the docs for Angular and I am unsure what it is meant to be.
$q.all().then(function () {
$scope.rData = rData;
});
Thanks for any help.

$http call always returns a promise which can be used with $q.all function.
var one = $http.get(...);
var two = $http.get(...);
$q.all([one, two]).then(...);
You can find more details about this behaviour in the documentation:
all(promises)
promises - An array or hash of promises.
In your case you need to create an array and push all the calls into it in the loop. This way, you can use $q.all(…) on your array the same way as in the example above:
var arr = [];
for (var a = 0; a < subs.length; ++a) {
arr.push($http.get(url));
}
$q.all(arr).then(function (ret) {
// ret[0] contains the response of the first call
// ret[1] contains the second response
// etc.
});

Related

Promise All Syntax and the Frame of accepatble Operations

The Function Promise.all - does have the job, to wait of asynchronious Functions and do certain logic after those asynchronious functions are done. I couldn't find how exactly does this function work i.e. if you can do certain things.
Example:
In this Code the Upperhalf till Promise.all, does fill the datelist with data. The Asynchronious function attachRequestCompleted - must first be done, so that datelist will be filled with data.
Within Promise.all i want to iterate through the datelist which was filled with data in attachRequestCompleted, so that i can later add them as Special Dates in the Calendar
var datelist = [];
var oModel = new sap.ui.model.json.JSONModel();
console.log(oModel.oData, datelist.length, datelist);
oModel.attachRequestCompleted(function() {
var oFeiertageBerlin = oModel.getData().BE;
for (var prop in oFeiertageBerlin) {
datelist.push(oFeiertageBerlin[prop].datum);
}
});
var jDatum = new Date();
var jLink = "https://feiertage-api.de/api/?jahr=" + jDatum.getFullYear();
oModel.loadData(jLink);
Promise.all([
this.oModel.attachRequestCompleted
]).then(
for (var j = 0; j < datelist.length; j++) {
console.log(datelist[j]);
}
)​
Expected Result: Possibility to iterate through the List
Actual Result: Syntax Error
As was indicated by the other comments, for the Promise function you would need to wrap the event callback in a Promise. However, your last comment lets me to believe that your root issue with the "this.byId" is that you'll need to bind the context for the callback. Hence:
oModel.attachRequestCompleted(function() {
// now this.byId(...) should work here
}.bind(this));
However, I would propose not using this setup and avoid the loadData function of the JSON model. The attaching of event handlers for completed / failed etc seems not be very elegant for me. I would rather go for jQuery.ajax() (https://api.jquery.com/jQuery.ajax/) or a manual XMLHttpRequest.
With jQuery it would look like this:
var oRequest = jQuery.ajax({
url: jLink
});
oRequest.done(function() {
// this.byId(...)
}.bind(this));

Create an array of fetch promises using a for loop with JavaScript/ES6 that can be read via Promise.all?

So, without boring anyone with the backstory, I need to access data from a number of APIs in order to run my script. The data needs to all be loaded before I execute the script, which I'm normally comfortable doing: I just declare some fetch requests, write a Promise.all, then continue on with the function.
HOWEVER, I've hit something of a snafu with a certain API that limits the number of results I can pull from one request to 100 and I need to query all of the results. I didn't think this was a huge deal since I figured I can just make a couple extra requests by affixing "&page=X" to the end of the request.
The plan, then, is to request the total number of pages from the API and then feed that into a for loop to push a number of fetch requests into an array of promises (i.e., link://to/api/data&page=1, link://to/api/data&page=2, etc). When I actually attempt to create this array with a for loop, though, the array returns empty. Here's my work:
const dataUrlNoPage = 'link/to/api/data&page=';
const totalPages = 3; //usually pulled via a function, but just using a static # for now
let apiRequestLoop = function(inp) {
return new Promise(function(resolve){
let promiseArray = [];
for (let i = 1; i <= inp; i++) {
let dataUrlLoop = dataUrlNoPage + i;
fetch(dataUrlLoop).then(function(response){
promiseArray.push(response.json());
})
}
resolve(promiseArray);
})
}
let finalPromiseArray = apiRequestLoop(totalPages).then(result => {
let requestArray = [apiRequest1,apiRequest2];
//requestArray contains earlier fetch promises
result.forEach(foo => {
requestArray.push(foo);
}
);
return requestArray;
});
So, what's tripping me up is really the loop, and how it's not returning an array of promises. When I look at it in the console, it shows up as a blank array, but I can expand it and see the promises I was expecting. I see the "Value below was evaluated just now" response. No matter how many promises or .thens, I write, however, the array is never actually populated at run time.
What's going on? Can I not generate fetch promises via a for loop?
(Also, just to cut this line of questioning off a bit, yes, the API I'm trying to access is Wordpress. Looking around, most people suggest creating a custom endpoint, but let's assume for the purpose of this project I am forbidden from doing that.)
You have several problems here.
The first is that you have the function provided to new Promise itself containing promise creations. Don't do this! It's a definite anti-pattern and doesn't keep your code clean.
The second is this basic bit of code:
let promiseArray = [];
for (let i = 1; i <= inp; i++) {
let dataUrlLoop = dataUrlNoPage + i;
fetch(dataUrlLoop).then(function(response){
promiseArray.push(response.json());
})
}
resolve(promiseArray);
This says:
create an empty array
loop through another array, doing HTTP requests
resolve your promise with the empty array
when the HTTP requests are completed, add them to the array
Step four will always come after step three.
So, you need to add the promises to your array as you go along, and have the overall promise resolve when they are all complete.
let apiRequestLoop = function(inp) {
let promiseArray = [];
for (let i = 1; i <= inp; i++) {
let dataUrlLoop = dataUrlNoPage + i;
promiseArray.push(fetch(dataUrlLoop).then(function(response) {
return response.json();
}));
}
return Promise.all(promiseArray);
}
or, with an arrow function to clean things up:
let apiRequestLoop = function(inp) {
let promiseArray = [];
for (let i = 1; i <= inp; i++) {
let dataUrlLoop = dataUrlNoPage + i;
promiseArray.push(fetch(dataUrlLoop).then(response => response.json()));
}
return Promise.all(promiseArray);
}
A few points:
you want to actually put the promises themselves into the array, not push to the array in a .then() chained to the promise.
you probably want to skip creating a new Promise in your function. Just get an array of all the promises from your loop, then return a Promise.all on the array.
Like this:
let apiRequestLoop = function(inp) {
let promiseArray = [];
for (let i = 1; i <= inp; i++) {
let dataUrlLoop = dataUrlNoPage + i;
promiseArray.push(fetch(dataUrlLoop))
}
return Promise.all(promiseArray);
}
In your final .then statement, in finalPromiseArray, your result will be an array of the results from all the promises. like this [response1, response2, response3, ...]
See the Promise.all documentation for more details.

How to have a variable number of Bluebird Promises? (Nodejs)

I have an empty array of documents.
let arrayOfDocuments = [];
I want to call http requests (using superagent) to download a text file and put its contents into my arrayOfDocuments.
request.get('docs.google.com/document/d/SOME_FILE_NAME').then((res) => {
arrayOfDocuments.push(res.text);
});
That part I get, but here is the tricky part. I want to put this in a for loop and do something after the for loop. So like:
for (let i = 0; i < numOfLinks; i++) {
// send the http requests as above
}
//do stuff here but only after the above for loop is finished.
How do I only do the last line if the loop is finished? The way my program is running right now, the code after the for loop runs before the http requests get a response and finish. I think there is a way to do this using Bluebird Promises, but I'm not sure. Thanks!
You can use the Promise.map method:
Promise.map(arrayOfLinks, function(link) {
return request.get(link);
}).then(function(arrayOfDocuments) {
// ...
});
Use promise.all as shown here http://bluebirdjs.com/docs/api/promise.all.html
In practice it might look something like:
var promises = []
var links = ['a.com/a', 'a.com/b']
for (let i = 0; i < links.length; i++) {
promises.push(request.get(links[i])
}
Promise.all(promises).then(function(allRes) {
//do anything you want with allRes or iterate
for (var promise in promises){
promise.then(function(singleRes){/*do something with each promise after all resolve*/}
}
});

Wait for loop to finish $.getJSON for each array item before outputting data

I've got an array of names which I need to retrieve data from, and I'm currently doing this with $.getJSON inside a loop. It works and I can retrieve the data, but to output the correct value I need to use setTimeout or similar. I'm wondering if there's a more refined way of doing what I'm looking to achieve.
Here's what I've got.
var names = ["riotgames", "example"];
var online = [];
for (var i = 0; i < names.length; i++) {
$.getJSON('https://api.twitch.tv/kraken/streams/' + names[i], function(data) {
if (data.stream != null) {
online.push(data.stream.channel.display_name);
};
});
}
console.log(online) // outputs []
setTimeout(function() {
console.log(online) // outputs correctly
}, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
When doing $.getJSON, you are triggering asynchronous requests. This means they run in the background. You do not wait for them to finish, their callbacks will trigger (like an event) once the request is done.
This means you cannot access online from outside the callback(s).
If you want to "wait" for all the requests to finish, then I suggest using promises. You can use $.when to combine all the requests into one promise then run a callback once everything is done.
var names = ["riotgames", "example"];
var promises = [];
for (var i = 0; i < names.length; i++) {
// $.getJSON returns a promise
promises.push($.getJSON('https://api.twitch.tv/kraken/streams/' + names[i]));
}
// Combine all promises
// and run a callback
$.when.apply($, promises).then(function(){
var online = [];
// This callback will be passed the result of each AJAX call as a parameter
for(var i = 0; i < arguments.length; i++){
// arguments[i][0] is needed because each argument
// is an array of 3 elements.
// The data, the status, and the jqXHR object
online.push(arguments[i][0].stream.channel.display_name);
}
console.log(online);
});

AngularJS collecting data in a loop of $http calls

I have the following loop which I have to make on my client API. On each iteration of loop I must add data returned from the API call as an object to an object array, then at the end of the loop I need to display the content of the object array.
Due to the nature of JS code execution (asynchronous) displaying the object array content always return undefined, so I was wondering if someone can please help me with a solution to this problem. Thanks.
var invoiceObj = {};
var invoiceObjArray = [];
for (var i=0; i< 5; i++)
{
//getAllInvoices returns a promise from $http.GET calls...
ClientInvoiceService.getAllInvoices(i).then(function(invoice){
invoiceObj = { invoiceNum: invoice.Result[0].id,
clientName: invoice.Result[0].clientName};
invoiceObjArray.push(invoiceObj);
}, function(status){
console.log(status);
});
}
console.log(invoiceObjArray[0]); //return undefined
console.log(invoiceObjArray[1]); //return undefined
What you'll need to do is store all promises and then pass these to $q.all (scroll all the way down), which will wrap them in one big promise that will only be resolved if all are resolved.
Update your code to:
var invoiceObj = {};
var invoiceObjArray = [];
var promises = [];
for (var i=0; i< 5; i++)
{
//getAllInvoices returns a promise...
promises.push(ClientInvoiceService.getAllInvoices(i).then(function(invoice){
invoiceObj = { invoiceNum: invoice.Result[0].id,
clientName: invoice.Result[0].clientName};
invoiceObjArray.push(invoiceObj);
}, function(status){
console.log(status);
}));
}
$q.all(promises).then(function(){
console.log(invoiceObjArray[0]);
});
This Egghead video is a nice tutorial to using $q.all:

Categories

Resources