AngularJS promise gets resolved with the wrong value - angularjs

I was planning on making a service that caches server response data in AngularJS and this is what I did:
function addDataCachingServiceToModule(module) {
module.factory('myDataCaching', function ($q, itemRepository) {
var categoriesWithNewItems = undefined;
function getCategoriesWithNewItems(date) {
var deferred = $q.defer();
if (!categoriesWithNewItems) {
return itemRepository.getCategoriesWithNewItems(date)
.then(function (res) {
if (res.data.Data.Success) {
categoriesWithNewItems = res;
deferred.resolve(res);
} else {
deferred.reject(res);
}
});
} else {
deferred.resolve(categoriesWithNewItems);
}
return deferred.promise;
}
function resetCategoriesWithNewItems() {
categoriesWithNewItems = undefined;
}
return {
getCategoriesWithNewItems: getCategoriesWithNewItems,
resetCategoriesWithNewItems: resetCategoriesWithNewItems
};
});
}
To my shock, it seems that while this works normally, when I try to use it like this:
myDataCaching.getCategoriesWithNewItems(date)
.then(function(res2) {
// res2 = undefined here !!!
});
..I get undefined instead of the data that I pass in in deferred.resolve(res);.
I have debugged this and it calls the 1st deferred.resolve(res); in my service with valid data, but in my then() I get undefined instead.
It never passes through any of the other resolve()/reject() calls.
Does anyone have any idea what's wrong here?

So, Anant solved your missing return issue. Let's discuss how you won't have it any more in the first place :)
Promises chain, your current code has the deferred anti pattern which we'd rather avoid. You're creating excess deferred which you shouldn't. Your life can be a lot easier:
function addDataCachingServiceToModule(module) {
module.factory('myDataCaching', function ($itemRepository) {
var cats = null; // prefer null for explicit lack
function getCategoriesWithNewItems(date) {
// are we sure we don't want to cache by date?
return cats = (cats || itemRepository.getCategoriesWithNewItems(date))
}
function resetCategoriesWithNewItems() {
categoriesWithNewItems = null;
}
return {
getCategoriesWithNewItems: getCategoriesWithNewItems,
resetCategoriesWithNewItems: resetCategoriesWithNewItems
};
});
}
So, what did we do here:
Cache the promise and not the value to avoid race conditions. In your original version making multiple requests before the first one returned and then updating the cache multiple times which would have made deleting not work.
Avoid creating an excess deferred, this is faster and cleaner too.

Related

Can't read folder from firebase storage bucket

I'm new to both react and firebase. I'm trying to get a string of file names (just as a continuous string for right now) from a particular folder in the firebase storage bucket. My code is as follows:
getFilesList(){
var toReturn="";
toReturn+="Testing";
var storage = firebase.app().storage(".....");
var storageRef = storage.ref();
var listRef = storageRef.child("/slides/1HdDPbTarLBxzllDlA3H/Lecture1/");
listRef.listAll().then(function(result) {
result.items.forEach(function(imageRef) {
toReturn+=this.getImageString(imageRef);
});
}).catch(function(error) {
toReturn+="error1";
});
return toReturn;
}
getImageString(imageRef) {
var toReturn="T1";
imageRef.getDownloadURL().then(function(url) {
var toReturn=url.toString();
}).catch(function(error) {
toReturn+="error2";
});
return toReturn;
}
I'm not getting any errors, but the return string is blank (aside from the 'Testing' prefix). The folder has about 20 jpg files, but it seems as though it isn't seeing that they are there. I did some research online about it, but I'm still not sure why my code isn't working. Please help me to know why this isn't working?
Thank you,
Jared
This isn't a problem with your framework, it's a problem with asynchronous code.
See, toReturn will eventually update with the results of that function call. The problem is that the caller of this function receives the array immediately, before any of the code in .then has a chance to touch it.
Even if the promise resolves instantly, the code above it will have already executed. Promises get put on a queue where the top item only executes after the current synchronous code has finished.
What getFilesList needs to do is return a Promise that the caller can then attach to in order to specify its own behaviour:
getFilesList(){
var toReturn="";
toReturn+="Testing";
var storage = firebase.app().storage(".....");
var storageRef = storage.ref();
var listRef = storageRef.child("/slides/1HdDPbTarLBxzllDlA3H/Lecture1/");
listRef.listAll().then(function(result) {
result.items.forEach(function(imageRef) {
toReturn+=this.getImageString(imageRef);
});
return toReturn;
}).catch(function(error) {
toReturn+="error1";
return toReturn;
});
And in the caller:
getFilesList.then(filesList => console.log(filesList))

call on function returns undefined on first call

In my controller i am calling this function in my service. idservice.getid()
I am testing it by printing it to console using console.log(idservice.getid())
and it returns undefined the first time, but after that if i call it again it returns the value.
I understand this is a async issue but im not sure how to make this work.
my service is below:
function idservice (userauth) {
var id;
this.getid = function() {
userauth.currentUser().then(function(user) {
id = user.id;
});
return id
}
}
How can i make it so that on the first call it doesnt return undefined? Is this a async issue?
This's happening because inside userauth currentUser() method you're making http call and response ('user.id') is yet not available. You can return the userauth.currentUser() call inside getid() method & also return id inside its success callback then. So your service method should look like
function idservice (userauth) {
var id;
this.getid = function() {
return userauth.currentUser().then(function(user) {
id = user.id;
return id;
});
}
}
And inside controller you should handle it like
idservice.getid().then(function(response){
$scope.id = response;
});
Here's small example of your requirement: https://plnkr.co/edit/bEjR9e179aRPfJiaQpei?p=preview
I've encountered this problem today, seems like if you request some data from the server and you assign it to a variable THEN you try to print it, it will show undefined on the first call, I think this is not something it should happen since you are trying to print it AFTER you got the information, but whatever.
I fixed it by removing that variable, just got the data then printed it.
I think this will solve your problem (the OP's last login is 2 years ago, but maybe it will help somebody else that encountered this and didn't found a useful answer?)
function idservice (userauth) {
this.getid = function() {
return userauth.currentUser().then(function(user) {
return user.id;
});
}
}
This will return the user.id as it is, it will not store it in a variable, you want it to be stored in a variable ? Store it, but don't print that variable, something like this :
function idservice (userauth) {
var id;
this.getid = function() {
return userauth.currentUser().then(function(user) {
id = user.id;
return user.id;
});
}
}
This worked for me (or at least the logic behind it worked).

How can I synchronize two $resource calls in AngularJS

I am trying to do something like the following:
In my controller I have functions that use $recource call to get data from database. The service 'myService'
var fillSubData = function (containerToFill) {
resService.getSubDataFromDB(//$resource service
{params},
function (res) {
//do something with containerToFill with the result res add new values
}
);
}
var fillData = function (containerToFill) {
resService.getDataFromDB(//$resource service
{params},
function (res) {
//do something with containerToFill with the result res
fillSubData(containerToFill);
}
);
}
Controller
$scope.dataToFill;// object
var initialize = function () {
//by reference
myService.fillData(dataToFill);
// I need the dataToFill filled to do other thing with data recovered and built
angular.forEach(dataToFill.someArrayBuilt, function (item) {
//do something with item...
})
}
I need the dataToFill filled to do other thing with data recovered and built, but the resource calls are asyn, how can I do this?
Note that the resource actions return an object that contains a $promise property. You can use this to proceed with a callback once the async call has returned:
myService.fillData(dataToFill).$promise.then(function() {
// I need the dataToFill filled to do other thing with data recovered and built
angular.forEach(dataToFill.someArrayBuilt, function (item) {
//do something with item...
})
});
To enable this, I suggest that you simply have your fillData method return the result of the resource call:
var fillData = function (containerToFill) {
return resService.getDataFromDB(//$resource service ...

How to roll back changes when there is an error in a promise chain

In my angular app I want to make changes to several locations in my firebase with a mix of transactions and set. I have written a promise chain with a little help. Now I need to handle any errors that may occur.
In the event of an error on any of the promises I would want to roll back any changes made in firebase (the successful promises) and alert the user to the failure.
Current code below
$scope.addNewPost = function() {
var refPosts = new Firebase(FBURL).child('/posts').push();
// Get tags into array for incrementing counters
var tags = $scope.post.tags.split(', ');
var allPromises = [];
// Iterate through tags and set promises for transactions to increment tag count
angular.forEach(tags, function(value, index){
var dfd = $q.defer();
var refTag = new Firebase(FBURL).child('/tags/' + value);
refTag.transaction( function (current_value) {
return current_value + 1;
}, function(error, committed, snapshot) {
if (committed) {
dfd.resolve( snapshot );
} else {
dfd.reject( error );
}
});
allPromises.push( dfd.promise );
});
// Add promise for setting the post data
var dfd = $q.defer();
refPosts.set( $scope.post, function (error) {
if (error) {
dfd.reject(error);
} else {
dfd.resolve('post recorded');
}
});
allPromises.push( dfd.promise );
$q.all( allPromises ).then(
function () {
$scope.reset(); // or redirect to post
},
function (error) {
// error handling goes here how would I
// roll back any data written to firebase
alert('Error: something went wrong your post has not been created.');
}
);
};
So what I need to know is how do I roll back any changes that happen to my firebase data in the event that one of these promises fail. There could be any number of updates happening in firebase. (for example: 3 tags being incremented via transaction and the post data being set)
How would I write the failure function to calculate what was successful and undo it? If this is this even possible.
--------------- sub question from original post has been solved ---------------
Also how do you force errors? I've tried setting a variable like below but it doesn't seem to work, is there something wrong with my .then?
refPosts.set( $scope.post, function (error) {
var forceError = true;
if (forceError) {
dfd.reject(forceError);
} else {
dfd.resolve('post recorded');
}
allPromises.push( dfd.promise );
});
There are two instances of this line, and they are both in the wrong place:
allPromises.push( dfd.promise );
In the first block, it should be in the last statement in the forEach callback, not in the transaction callback.
In the second block, it should be after the call to set(), not in the callback.
The way your code is written now, $q.all() is getting an empty array of promises. That could also be what's interfering with the forceError test you're attempting.

Caught in Restangular promises

I've got this code:
var me = Restangular.one('object', 1).get({params:params});
me.then(function (data) {
$scope.me = data;
$scope.loadsubobject();
});
$scope.loadsubobject = function () {
$scope.me.getList("subobject")
.then(function(subobject) {
//the code never gets here
console.log('loaded subobjects', subobjects);
$scope.subobjects = $scope.subobjects.concat(subobject.results);
if (data.next){
$scope.next = data.next;
}
},function(response){
console.log('Error with response:', response.status)
});
when I try to debug the code It seems that after calling the $scope.me.getList("subobject")It returns to the first thenand never getting to the second then, the one that actually process the subobjects I need.
Any clue out of call back hell?
I verified that the server does return the correct answer
How can I fix that? be glad for help with this
turned out to be a completely different issue, the json returned wasn't "flat", so I need to use a responseExtractor as explained here

Resources