Robin Robin - 1 month ago 3
AngularJS Question

Fetching firebase objects in loop with promises

I need some help with promises if possible. I'm new to AngularJS and can't figure out how to deal with the problem I'm having. Basically, the issue is that I have two different arrays that are initialized asynchronously, while ultimately storing both data in the same array and doing this in a for-loop for multiple items. The bug I'm encountering is that while it loads the correct amount of items, it shows all of the different user data, but not a different picture. The picture that is shown is the same for each entry. At the end of the entire code section you find this:

userPromise.then(function(user){
picPromise.then(function(url){
newfriendsinfo.push({
id: newfriendid,
name: user.val().name,
email: user.val().email,
agreed: newfriendagreed,
profilepicture: url
});
}).then(function(){
if (newfriendsinfo.length == newfriends.length){
deferred.resolve(newfriendsinfo);
}
});
});


I am quite sure this is where my problem is. It looks for new user data while not using a new picture. However, I am not sure how I can deal with this problem. I have looked into multiple deferred variables, and $q.all, but I can't quite see how I'm supposed to tackle the issue. Below you find the entire relevant code. Thanks a lot for any help :)

var friendsRef = firebase.database().ref('friendships/' + firebase.auth().currentUser.uid);

$scope.friends = $firebaseArray(friendsRef);

$scope.friendsinfo = [];

$scope.$watch('friends', function() {
var newfriends = $scope.friends;

asyncUpdateFriendsInfo(newfriends).then(function(newlist){
$scope.friendsinfo = newlist;
});
}, true);

function fetchPicture(ref){
return ref.getDownloadURL().then(function(url) {
return url;
}).catch(function(error) {
alert("error");
});
}

function fetchUserInfo(ref){
return ref.once('value', function(snapshot){

}).then(function(snapshot){
return snapshot;
});
}

function asyncUpdateFriendsInfo(newfriends){
var deferred = $q.defer();
var newfriendsinfo = [];

for(var i = 0; i < newfriends.length; i++){
var ref = firebase.database().ref('users/' + newfriends[i].$id);
var profilePicRef = firebase.storage().ref("profilepictures/" + newfriends[i].$id + "/profilepicture");
var userPromise = fetchUserInfo(ref);
var picPromise = fetchPicture(profilePicRef);

var newfriendid = newfriends[i].$id;
var newfriendagreed = newfriends[i].agreed;

userPromise.then(function(user){
picPromise.then(function(url){
newfriendsinfo.push({
id: newfriendid,
name: user.val().name,
email: user.val().email,
agreed: newfriendagreed,
profilepicture: url
});
}).then(function(){
if (newfriendsinfo.length == newfriends.length){
deferred.resolve(newfriendsinfo);
}
});
});
}

return deferred.promise;


}

Answer

The problem is definitely in this code.

userPromise.then(function(user){
    picPromise.then(function(url){

You have nested promises and this does not guarantee that userPromise will be resolved first and picPromise will be resolved second.

They are two independent asynchronous calls. In case if picPromise is resolved first the following code will never be called.

newfriendsinfo.push({
    id: newfriendid,
    name: user.val().name,
    email: user.val().email,
    agreed: newfriendagreed,
    profilepicture: url
});

Besides that, even if first userPromise is resolved and then picPromise you will still have problems. You are using variables newfriendid and newfriendagreed in promise which is created outside of promise in cycle. Here you have problem of Closures.

Here is what will happen when asyncUpdateFriendsInfo function is called.

When for cycle is finished all request will be sent (but responses are not received yet) and newfriendid and newfriendagreed point to newfriends's last record's $id and agreed. So in newfriendsinfo all newfriendid will be the same and will be last newfriendid.

Check out this question for "Asynchronous Process inside a javascript for loop"

If fact you should replace this code

userPromise.then(function(user){
    picPromise.then(function(url){
        newfriendsinfo.push({
            id: newfriendid,
            name: user.val().name,
            email: user.val().email,
            agreed: newfriendagreed,
            profilepicture: url
        });
    }).then(function(){
        if (newfriendsinfo.length == newfriends.length){
            deferred.resolve(newfriendsinfo);
        }
    });
});

into something like this

(function(newfriendid){
    var finalUser, 
        finalUrl;

    userPromise.then(function(user){
        finalUser = user;
        checkIfBothLoaded();
    });

    picPromise.then(function(url){
        finalUrl = url;
        checkIfBothLoaded();  
    });

    function checkIfBothLoaded(){
        if (finalUser && finalUrl){
            newfriendsinfo.push({
                id: newfriendid,
                name: finalUser.val().name,
                email: finalUser.val().email,
                agreed: newfriendagreed,
                profilepicture: finalUrl
            });
        }

        if (newfriendsinfo.length == newfriends.length){
            deferred.resolve(newfriendsinfo);
        }
    }

})(newfriendid, newfriendagreed);