promises only working properly with then function - javascript

I have a button which executes a function with a promise that gets and displays data from firebase in html (I'm using angularJS, ionic and firebase).
The problem is : if I don't inlclude a .then(function(){}) after it, the promise gets executed in unsynchronous way, meaning I have to click the button once again so the data gets displayed in html.
I want to put the data in the scope after the promise (that gets the data from firebase), but for some reason in only works if I put a .then function after it.
However, the data gets displayed normally in the console, but not in html (meaning I think that the function doesn't get attached to the scope).
Here is the piece of code :
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
$scope.chatArray = snapshot.val();
deferred.resolve()
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
return deferred.promise.then(function(){
// Removing this empty .then(function(){}) function
// will result in asynchronousity.
// just "return deferred.promise;" doesn't work.
})
}
Any solutions? I don't have much experience with promises but I didn't find anything related. Cheers.

the purpose of promise is to manage asynchronous methods, so I don't really understand the problem...
Moreover normally the code inside displayChat must be executed, only the callback must be executed after. You should return the promise, that enable you to execute callback once you are sure the required asynchronous method are done.

When changes to scope are done by events external to the AngularJS framework, the framework needs to do an $apply to initiate a digest cycle to update the DOM.
(source: angularjs.org)
The .then method of a $q service promise automatically initiates the necessary digest cycle. In this case, the promise returned by the displayChat function is discarded and no digest cycle gets initiated. The subsequent click of the button initiates the digest cycle.
In the future, someone might wish to chain from the promise returned by the displayChat function. I recommend making the function more versatile by returning a proper promise and moving any changes to scope into a .then method.
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
//ref.on("value", function(snapshot) {
//USE once
ref.once("value", function(snapshot) {
console.log(snapshot.val());
//$scope.chatArray = snapshot.val();
deferred.resolve(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
//ADD rejection case
deferred.reject(errorObject);
})
return deferred.promise.then(function(chatArray){
//Move scope changes here
$scope.chatArray = chatArray;
//RETURN to chain data
return chatArray;
// Removing this empty .then(function(){}) function
// will result in asynchronousity.
// just "return deferred.promise;" doesn't work.
})
}
Also to avoid memory leaks, use ref.once instead of ref.on and be sure to reject the promise in the error case.

Promises are used to defer execution of some logic until the promise has been satisfied - ie: you have received your results from the database. In your case, you are already deferring your console display and setting of the $scope variable within ref.on. The promise code is redundant.
The fact that your result shows in the console proves you have received the result. When you update data in the scope, it will not appear until a digest cycle has occurred. Most of the time, Angular can automatically figure out when it needs to run a digest cycle. On those times when it does not, you can force it by wrapping your scope related logic in a timeout, in which case, your code would look as follows:
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
$timeout(function () {
$scope.chatArray = snapshot.val();
});
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
}
Your use of the promise .then method just happened to trigger a digest cycle. The promise itself really wasn't doing anything.
If your intention was to pass the snapshot back to the caller when it became available, that's when the promise comes into play, and would be done as follows:
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
deferred.resolve(snapshot)
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
return deferred.promise;
};
$scope.callDisplayChat = function () {
$scope.displayChat().then(function (result) {
$scope.chatArray = result.val();
});
};

Related

Chart showing just after resizing window - Using chart.js and Firebase database [duplicate]

I'm using Firebase along with angularJS to fetch a list of users. I can read all the users from my database with the once() function, but I can't figure out why userList returns undefined below
.service('userService', [function() {
this.getUsers = function() {
var users;
var userList;
var ref = firebase.database().ref('/users/');
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
}).then(function(){
userList = users;
console.log(userList); // outputs all users
},(function(error){
alert('error: ' + error);
});
console.log(userList); // outputs 'undefined'
}
}]);
I wait to assign my userList variable until I'm done processing users, but no luck.
There is something important here that I am missing where Promises/callbacks are concerned and I cannot find it in the documentation; could someone help me understand my issue?
The problem is (as imjared says) that the data is read from Firebase asynchronously. So the code doesn't execute in the order that you think. It's easiest to see that by simplifying it with just a few log statements:
.service('userService', [function() {
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
console.log("before attaching listener");
ref.once('value').then(function(snapshot) {
console.log("got value");
});
console.log("after attaching listener");
}
}]);
The output of this will be:
before attaching listener
after attaching listener
got value
Knowing the order in which this executes should explain perfectly why you cannot print the user list after you've just attached the listener. If not, I recommend also reading this great answer: How to return the response from an asynchronous call
Now for the solution: you will either need to use the user list in the callback or return a promise.
Use the user list in the callback
This is the oldest way to deal with asynchronous code: move all code that needs the user list into the callback.
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
}
console.log(users); // outputs all users
})
You're reframing your code from "first load the user list, then print its contents" to "whenever the user list is loaded, print its contents". The difference in definition is minor, but suddenly you're perfectly equipped to deal with asynchronous loading.
You can also do the same with a promise, like you do in your code:
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
});
But using a promise has one huge advantage over using the callback directly: you can return a promise.
Return a promise
Often you won't want to put all the code that needs users into the getUsers() function. In that case you can either pass a callback into getUsers() (which I won't show here, but it's very similar to the callback you can pass into once()) or you can return a promise from getUsers():
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
return ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
return(users);
}).catch(function(error){
alert('error: ' + error);
});
}
With this service, we can now call getUsers() and use the resulting promise to get at the users once they're loaded:
userService.getUsers().then(function(userList) {
console.log(userList);
})
And with that you have tamed the asynchronous beast. Well.... for now at least. This will keep confusing even seasoned JavaScript developers once in a while, so don't worry if it takes some time to get used to.
Use async and await
Now that the function returns a promise, you can use async/await to make the final call from above look a bit more familiar:
function getAndLogUsers() async {
const userList = await userService.getUsers();
console.log(userList);
}
You can see that this code looks almost like a synchronous call, thanks to the use of the await keyword. But to be able to use that, we have to mark the getAndLogUsers (or whatever the parent scope of where we use await) as async, which means that it returns a Future too. So anyone calling getAndLogUsers will still need to be aware of its asynchronous nature.
You need to think async:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
console.log(users); // outputs 'undefined'
}
}]);
I bet if you did this, you'd be fine:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
window.setTimeout( function() {
console.log(users); // outputs 'undefined'
}, 1500 );
}
}]);
Clarification per Franks's comment:
I should clarify further that the setTimeout would just prove that this is an issue of timing. If you use setTimeout in your app, you're probably going to have a bad time as you can't reliably wait n milliseconds for results, you need to get them then fire a callback or resolve a promise after you have gotten the snapshot of the data.

Unable to access Firebase database data outside of function in Vue [duplicate]

I'm using Firebase along with angularJS to fetch a list of users. I can read all the users from my database with the once() function, but I can't figure out why userList returns undefined below
.service('userService', [function() {
this.getUsers = function() {
var users;
var userList;
var ref = firebase.database().ref('/users/');
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
}).then(function(){
userList = users;
console.log(userList); // outputs all users
},(function(error){
alert('error: ' + error);
});
console.log(userList); // outputs 'undefined'
}
}]);
I wait to assign my userList variable until I'm done processing users, but no luck.
There is something important here that I am missing where Promises/callbacks are concerned and I cannot find it in the documentation; could someone help me understand my issue?
The problem is (as imjared says) that the data is read from Firebase asynchronously. So the code doesn't execute in the order that you think. It's easiest to see that by simplifying it with just a few log statements:
.service('userService', [function() {
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
console.log("before attaching listener");
ref.once('value').then(function(snapshot) {
console.log("got value");
});
console.log("after attaching listener");
}
}]);
The output of this will be:
before attaching listener
after attaching listener
got value
Knowing the order in which this executes should explain perfectly why you cannot print the user list after you've just attached the listener. If not, I recommend also reading this great answer: How to return the response from an asynchronous call
Now for the solution: you will either need to use the user list in the callback or return a promise.
Use the user list in the callback
This is the oldest way to deal with asynchronous code: move all code that needs the user list into the callback.
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
}
console.log(users); // outputs all users
})
You're reframing your code from "first load the user list, then print its contents" to "whenever the user list is loaded, print its contents". The difference in definition is minor, but suddenly you're perfectly equipped to deal with asynchronous loading.
You can also do the same with a promise, like you do in your code:
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
});
But using a promise has one huge advantage over using the callback directly: you can return a promise.
Return a promise
Often you won't want to put all the code that needs users into the getUsers() function. In that case you can either pass a callback into getUsers() (which I won't show here, but it's very similar to the callback you can pass into once()) or you can return a promise from getUsers():
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
return ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
return(users);
}).catch(function(error){
alert('error: ' + error);
});
}
With this service, we can now call getUsers() and use the resulting promise to get at the users once they're loaded:
userService.getUsers().then(function(userList) {
console.log(userList);
})
And with that you have tamed the asynchronous beast. Well.... for now at least. This will keep confusing even seasoned JavaScript developers once in a while, so don't worry if it takes some time to get used to.
Use async and await
Now that the function returns a promise, you can use async/await to make the final call from above look a bit more familiar:
function getAndLogUsers() async {
const userList = await userService.getUsers();
console.log(userList);
}
You can see that this code looks almost like a synchronous call, thanks to the use of the await keyword. But to be able to use that, we have to mark the getAndLogUsers (or whatever the parent scope of where we use await) as async, which means that it returns a Future too. So anyone calling getAndLogUsers will still need to be aware of its asynchronous nature.
You need to think async:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
console.log(users); // outputs 'undefined'
}
}]);
I bet if you did this, you'd be fine:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
window.setTimeout( function() {
console.log(users); // outputs 'undefined'
}, 1500 );
}
}]);
Clarification per Franks's comment:
I should clarify further that the setTimeout would just prove that this is an issue of timing. If you use setTimeout in your app, you're probably going to have a bad time as you can't reliably wait n milliseconds for results, you need to get them then fire a callback or resolve a promise after you have gotten the snapshot of the data.

Angular javascript data that is nested objects I cannot seem to get d.data.Array[2] to work

I had been using a angular service in which the data was returning just fine.
However, I wanted to instead call directly to the json file except now it doesn't like the data.
Working version
Controller code:
var confirm = this;
confirm.booking = airConfirmationService.getTestData();
Service code :
.factory('airConfirmationService', airConfirmationService);
var confirm = {};
confirmed.getTestData = function () {
return {
"flightData": [
{
"MultiCarrier": false,
// etc...
However I am switched to a service with .service and calling the location of the .json file up directly.
Not working ( well, it pulls the data but returns in a way that I don't understand)
.service('airConfirmationService', function ($http) {
this.getTestData = function () {
return $http({
url: '../apps/temp/Api_Responses/confirm.booking.json',
method: "GET"
})
}
});
Then in controller
var confirm = this;
confirm.booking = airConfirmationService.getTestData();
console.log(confirm.booking) // Picture attached shows how the data looks
// My attempt at getting "at" the data ...
//var temp = [];
//temp = airConfirmationService.getTestData();
//confirm.booking = temp.d.Data;
UPDATE
While this code below "works" I have a feeling that not doing "q" /defer / .then will be bad...
This code works in the controller calling the service, but how can i add/change to have q/defer and/or .then ?
var getData = airConfirmationService.getTestData();
getData.success(function(data) {
confirm.booking = data;
});
Angular $http is Asynchronous. The network operation to get the data from the server may take some time, so angular does not expect your app to wait until the operation is complete, stuck hung until the data is available. Therefore, a promise object is used as a placeholder for the data. Promises have a .then() function which tells the promise that it should execute some other code after the operation is complete. They also provide a .catch() function for when something goes wrong and the data isn't returned.
The correct way to use $http is:
var confirm = this;
confirm.testData = {};
airConfirmationService.getTestData()
.then(function(response) {
confirm.testData = response.data;
})
.catch(function(){
//something went wrong
});

Firebase, retrieving data asynchronously

I wrote a promise in javascript to delay an animation until some data is retrieved from my firebase database. The problem is that for some reason the promise isn't working, and the code isn't running asynchronously. Am i not using this promise properly, or is there another issue?
var name = document.querySelector('.name')
new Promise (function() {
firebase.database().ref('users/' + userId ).on('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
});
}).then(console.log('second'));
My issue is that when I check the console, I find that the code isn't running asynchronously, and I will see the messages logged in the wrong order, 'second' then 'first'.
The first problem is that (as I commented on cartant's answer) an on() handler can be invoked multiple times and thus cannot be treated as a promise.
If you only care about the current value of the node, you can use once(), which does return a promise:
var name = document.querySelector('.name')
firebase.database().ref('users/' + userId ).once('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
}).then(function() { console.log('second') });
Alternatively, if you do care about changes in the value too, you may want to keep on using on(). But in that case you should pass in a callback.
var name = document.querySelector('.name')
firebase.database().ref('users/' + userId ).on('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
});
If you want to explicitly detect the first value in this, do something like:
var name = document.querySelector('.name'),
isFirst = true;
firebase.database().ref('users/' + userId ).on('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
if (isFirst) {
console.log('second');
}
isFirst = false;
});

asynchronous callback returns null

I am trying to assign callback function values and display ng-repeat
var myApp = angular.module('mybet',[]);
myApp.controller('MyBetController', function($scope,$firebase,ProfileFactory,MybetFactory){
$scope.myBetEvents = MybetFactory.getUsersBetsProfile(currentLoggedUser, function(betsData){
//Callback retuning null value here
$scope.myBetEvents = betsData;
});
});
myApp.factory('MybetFactory', ['$firebase', function($firebase){
var factory = {};
factory.getUsersBetsProfile = function(userId, callback){
var firebaseRef = new Firebase("https://xxx.firebaseio.com/usersbetsprofile").child(userId);
var firebaseBetsRef = new Firebase("https://xxx.firebaseio.com/events");
var userBettedEvent = [];
//retrive the data
firebaseRef.on('value', function (snapshot) {
console.log(snapshot.val());
var data = snapshot.val();
if (data){
//callback(data);
angular.forEach(data, function(data){
console.log('For Each getUsersBets',data);
var firebaseEventsData = firebaseBetsRef.child(data.event_id);
//retrive the data of bets
firebaseEventsData.on('value', function (snapshot) {
userBettedEvent.push(snapshot.val());
console.log('userBettedEvent',userBettedEvent);
//if I call callback here then i get the values but calling callback multipul time is no the option
}, function (errorObject) {
console.log('The read failed: ' + errorObject.code);
});
});
//ISSUE:: callback returing null values
callback(userBettedEvent);
}else{
console.log('Users has no bets');
}
}, function (errorObject) {
console.log('The read failed: ' + errorObject.code);
});
};
return factory;
}])
View::
<ul class="list">
<li class="item" ng-repeat="events in myBetEvents">{{events.event_name}}</li>
</ul>
How can get callback() to return values and display in view? what is causing callback after foreach returning null?
As #coder already said in the comments, your callback(userBettedEvent) is in the wrong place. Where you have it now, it will be invoked before the on('value' from Firebase has completed.
From the comments it is clear that you've notice that, yet only want the callback to invoked for one child. You can simply use a limit for that.
Something like this would work:
if (data){
data.limit(1).on('value', function(childData) {
var firebaseEventsData = firebaseBetsRef.child(childData.event_id);
firebaseEventsData.on('value', function (snapshot) {
userBettedEvent.push(snapshot.val());
callback(userBettedEvent);
}
})
I haven't really tested the code, but I'm quite sure a limit should get you in the right direction.
As a side note: you seem to be using Firebase as a traditional database, pulling the data out like you'd do with SQL. As you may notice: that is not a natural model for Firebase, which was made to synchronize data changes. If you want to stick to pulling the data, you may consider using once('value' instead of on('value' to ensure the handlers fire only once for every time you try to pull the data from Firebase.

Categories

Resources