I have a need to make multiple concurrent calls to an Angular resource, and chain some actions with the $promise api.
I define a resource like this
myServicesModule.factory('MyResource', ['$resource', 'SETTINGS', function($resource, SETTINGS) {
return $resource(SETTINGS.serverUrl + '/myResource/:id', { },
{
get: { method: "get", url: SETTINGS.serverUrl + '/myResource/show/:id' },
}
);
}]);
My controller needs to retrieve multiple records, and take actions on each one when the record is ready. I am having trouble passing values to the then() closure.
When I do this:
for (var i = 0; i < 3; i++) {
MyResource.get({id: i}).$promise.then(function(item) { console.log(i); });
}
The output is "2, 2, 2".
This code results in the desired output of "0, 1, 2" (order varies depending on when each resource call completes), but this is an ugly solution.
for (var i = 0; i < 3; i++) {
var closure = function(i) {
return function(item) { console.log(i); console.log(item); }
}
UwgCarrier.get({id: i}).$promise.then( closure(i) );
}
Why does the first code snippet return "2, 2, 2" ?
Is there a cleaner way to solve this problem?
It's a matter of closures. Just wrap your closure in another one.
You can make a workaround with a call to an immediate function, that would look like :
for (var i = 0; i < 3; i++) {
(function(c) {
UwgCarrier.get({id: c}).$promise.then( console.log(c); );
})(i);
}
In my example I have replaced "i" by "c" into the closure to make things clear. Like so, the immediate function invoke "i" with its current value within the loop process.
I don't think that there is a better way to do this as it's a javascript concern.
EDIT :
As ES6 is coming soon, you can use the "let" keyword to achieve the same goal without wrapping your inner loop in a closure, because "let" block-scoped your variable.
for (let i = 0; i < 3; i++) {
UwgCarrier.get({id: i}).$promise.then( console.log(i); );
}
Related
I have a controller with a for loop that make's HEAD requests for an array of URLs to check if the file exists. When I get the response from the HEAD request, i need the index number from the array that the request was based on.
var allFiles = [], files = [];
allFiles.push({"url":"http://www.example.com/foo","source":"source1"});
allFiles.push({"url":"http://www.example.com/bar","source":"home"});
allFiles.push({"url":"http://www.example.com/wtf","source":"outer space"});
for(var i=0,len=allFiles.length;i<len;i++) {
$http.head(allFiles[i].url).then(function(response) {
files.push(allFiles[VALUE_OF_i_AT_TIME_OF_REQUEST]);
}
}
EDIT:
Because it is an asynchronous call, I cannot use i in place of VALUE_OF_i_AT_TIME_OF_REQUEST. Doing that results in i always being equal to len-1
I guess I can send the index number as data with the request and retrieve it from the response but for some reason that seems hack-ish to me.
Is there a better way?
You can do this with a function closure
for (var i = 0, len = allFiles.length; i < len; i++) {
function sendRequest(index) {
$http.head(allFiles[index].url).then(function (response) {
files.push(allFiles[index]);
});
}
sendRequest(i);
}
I may be oversimplifying this (asynchronous code is still tricky to me), but could you set i to a new local variable j on each loop then reference j instead of i in files.push(allFiles[j]):
var allFiles = [], files = [];
allFiles.push({"url":"http://www.example.com/foo","source":"source1"});
allFiles.push({"url":"http://www.example.com/bar","source":"home"});
allFiles.push({"url":"http://www.example.com/wtf","source":"outer space"});
for(var i = 0, len = allFiles.length; i < len; i++) {
var j = i;
$http.head(allFiles[i].url).then(function(response) {
files.push(allFiles[j]);
}
}
I did something similar to #rob's suggestion and it seems to be doing the trick.
var allFiles = [], files = [];
allFiles.push({"url":"http://www.example.com/foo","source":"source1"});
allFiles.push({"url":"http://www.example.com/bar","source":"home"});
allFiles.push({"url":"http://www.example.com/wtf","source":"outer space"});
for(var i=0,len=allFiles.length;i<len;i++) {
(function(i) {
$http.head(allFiles[i].url).then(function(response) {
files.push(allFiles[i]);
}
})(i);
}
Say I have a list:
var mylist = ["a","b","c","d"];
I want to request data for each one of these like so and get the responses back in the same order.
var comeback = [];
getMyData()
function getMyData() {
for (int i = 0; i < mylist.length; i++) {
$http.get("http://myurl/" + mylist[i]).success(function(data) {
results.append(data);
});
}
}
How can I make sure that the "comeback" list has all the responses based on "a", "b", etc. in the same order? What is the best way to write this?
Chaining the promises will make them execute in series. Something like:
var results = [];
getMyData(0);
function getMyData(i) {
return $http.get("http://myurl/" + mylist[i]).success(function(data) {
results.push(data);
i++;
if(i < mylist.length) {
getMyData(i);
}
});
}
Note: If you want to do more advanced validation and error checking, you would need to use $q.
I imagine this could be a pretty general problem, but in this case I'm using AngularJS and the SoundCloud API.
Here's the flow:
Call loadTracks()
loadTracks() should load the tracks of a SoundCloud user, 50 at a time, until the list runs out.
loadTracks() does this by calling another function, sc.getTracksByUser(id), which returns a promise
loadTracks() should update the variable $scope.tracks with each 50 track batch when it arrives
The SoundCloud API provides an option offset, so loading the batches is relatively easy. I think it's the promise that is tripping me up. Without the promise, the solution would be:
$scope.tracks = [];
var loadTracks = function() {
var page = -1,
done = false,
newTracks;
while (!done) {
newTracks = getFiftyTracks(page++);
for (var i = 0; i < newTracks.length; i++) {
$scope.tracks.push(newTracks[i]);
}
if (newTracks.length < 50) done = true;
}
}
Unfortunately, that line with getFiftyTracks in it is not how it works. The actual implementation (using a promise) is:
sc.getTracksByUser(id).then(function (response) {
for (var i = 0; i < response.length; i++) {
$scope.tracks.push(response[i]);
}
}
I'm guessing the solution to this is some sort of recursion, but I'm not sure.
You can do that in this way
sc.getTracksByUser(id).then(function (response) {
for (var i = 0; i < response.length; i++) {
$scope.tracks.push(response[i]);
}
// if response return 50 track call getTracksByUser again
if (response.length === 50) sc.getTracksByUser(id);
});
I have the following nested $http calls to two apis, problem I am facing is that I can't access outer $http call results from inner $http call even though outer $http call results is assigned to a separate variable. Can someone please tell me what I am missing here and how to fix it? Thanks
clientSvc.getInvoices(clientID).then(
function(clientInvoices) {
var invoiceID = '';
for (var i=0; i < clientInvoices.Result.length; i++) {
invoicesPromise.push(clientSvc.getAR(clientInvoices.Result[i].id).then(
function(ARList) {
//This will always return 3
console.log(i);
//following line raises an error id of undefined...
invoiceID = clientInvoices.Result[i].id;
},
function(status){
console.log(status);
}
));
}
$q.all(invoicesPromise).then(function() {
....
});
},
function(status){
console.log(status);
}
);
You reference i from the closure, but its value changes in the loop; when the then success functions do get called, i is clientInvoices.Result.length + 1, which is why clientInvoices.Result[i] is undefined. Use a separate function e.g. as:
for (var i=0; i < clientInvoices.Result.length; i++) {
invoicesPromise.push(callNested(clientInvoices.Result[i].id));
}
function callNested(resultId) {
return clientSvc.getAR(resultId).then(
function(ARList) {
invoiceID = resultId;
},
function(status){
console.log(status);
}
)
}
Still though, you are assigning to the single-valued variable invoiceID many times; this will cause problems. Also, there is no var invoicesPromise = []; in your code.
I'm trying to call a factory with $http. In a browser I check the network tab and I see that the call has come trough and that the data is there but for some reason this code:
$scope.naloziPodatke = function () {
if (typeof $scope.Obdobje_do === 'undefined' || typeof $scope.Obdobje_do === 'undefined') {
alert("Napaka. Obdobje ni nastavljeno.");
} else {
getData.gData($scope.Obdobje_od.toLocaleDateString("en-US"), $scope.Obdobje_do.toLocaleDateString("en-US")).success(function (d) {
console.log(d); $scope.data = d;
for (var i = 0; i < $scope.data.d.length; i++) {
var virTasks = [];
for (var j = 0; j < $scope.data.d.SeznamVirov.length; j++) {
virTasks.push({ id: $scope.data.d[i].SeznamVirov[j].Id, subject: "", from: $scope.data.d[i].SeznamVirov[j].ObdobjeOd, to: $scope.data.d[i].SeznamVirov[j].ObdobjeDo, color: $scope.barve[colorCount], data: { id: $scope.data.d[i].SeznamVirov[j].Vir } });
}
$scope.dasData.push({
id: $scope.data.d[i].Id, name: $scope.data.d[i].Naziv, tasks: virTasks
});
if (colorCount + 1 === barve.length) {
colorCount = 0;
} else {
colorCount++;
}
}
console.log($scope.dasData);
});
}
}
Returns an error: Cannot read property 'd' of undefined.
d should be an array that .net serializer makes. It's there, browser sees it but angular does not.
You will notice that i have a console.log(d); before the for loop starts and this is the screenshot (dont get how console.log gets the data but the for loop doesnt (it's the same if i try to loop trough d or if i'm saving it's reference into $scope.data):
The service is very simple:
hermesGantt.factory('getData', ['$http', function ($http) {
return {
gData: function (obdOd, obdDo) {
return $http({ method: 'POST', url: 'index.aspx/Filtercasvir', data: { 'odObd': obdOd, 'doObd': obdDo } });
}
}
}]);
Any ideas?
You have a bug in second inner loop. Correct is:
for (var j = 0; j < $scope.data.d[i].SeznamVirov.length; j++) ...
it's not possible what you have write, data.d.something. maybe it's data[index].d[index] etc.. etc.. to let you know it, do the console.log(data) so you can see the entire response fields
EDIT
seeing your image it's clear what i have said before, d is a field that has as a value an array. To access at any value you need to put the index of the elements value in the d field. take a look at the documentation for the json array, it is really use full in angularjs.