Passing value out from reading function of firebase realtime database - javascript

I am trying to passing out the value which located in the reading function of firebase which is forEach loop. Yet I can't figure out why it does not work with the following code.
var object = {};
var objectKey = objectKey ? objectKey : firebase.database().ref().child('test').push().key;
object[objectKey] = {a:"1", b:"2", c:"3"};
firebase.database().ref('test').update(object);
var objRef = firebase.database().ref('test');
var displayObj;
var total = 0;
objRef.on('value', function(snapshot) {
snapshot.forEach(function(childSnapshot){
displayObj = childSnapshot.val();
var a = displayObj.a;
var b = displayObj.b;
var c = displayObj.c;
total = a + b + c;
console.log(total); //expected result: 123
return total;
});
});
console.log(total); //I wish to set total = 123 but instead
//it return the original value, 0
For your information, the writing function and reading function are working well as I have tested through the firebase console.
Thank you in advance.

objRef.on is asynchronous, meaning it returns immediately before results are available to the callback function. The callback function is executed some time later with the snapshot of the data you've asked for.
On top of that, your return value coming from return total is being returned from the callback function, which is meaningless. It has no bearing on the value of the log line following the callback.
Going even further, the callback you passed to on() is going to be called repeatedly as the data at the location pointed to by objRef changes. This is probably not what you want. If you want a single snapshot at a location, use once() instead of on().
The bottom line is this: Realtime Database callbacks are asynchronous, so if you want to use a value from a snapshot coming from Realtime Database, deal with it only after the callback in invoked. Please read this blog to know more about why Firebase APIs are asynchronous.

Related

Firebase web application - Calling gotData function

I am building a web app with JS and firebase as my DB, but I'm struggeling to call on my gotData function to get it's return value.
var dbRef = firebase.database().ref('testing');
function reading(){
dbRef.on('value', gotData,errData) ;
}
function gotData(data){
var tests = data.val();
var keys = Object.keys(tests);
var length = keys.length;
var k = keys[lengthe-1];
console.log(k)
console.log(data)
return k;
}
function errData(err){
alert('error')
}
The code works and I get the documentID of the newest document returned, but I fail to call on this value.
I tried:
var fbid = gotData(data);
but I get an data is not defined error. From my understanding data is just used to reference to the Firebase data so I can do operations on it. What do I need to do, so that I can call the function with it's data ?
You're calling gotData() yourself at the bottom of the code in the jsbin, without passing in a parameter:
var id = gotData();
How I found this:
Put this line as the first statements inside gotData():
if (!data) debugger
Run the code in the jsbin again, which starts the debugger now.
Check the call stack (on the right hand side in the Chrome debugger) to see where the call came from.
You'll want to remove this gotData() call, and put all code that needs the data inside the gotData() functions. Also see:
Passing variable in parent scope to callback function
Firebase Query inside function returns null
Why Does Firebase Lose Reference outside the once() Function?
Is there any way to break Javascript function within an anonymous function?

How to pass additional variables into a Firebase promise's callback in Javascript?

We are trying to pass additional variables into a Firebase .ON promise's callback.
We have looked at dozens of posts on SO describing how to pass static variables into callback functions for .ON methods - but each version is throwing a data.val() undefined error for us.
Here is sample code. We want to pass in a value for i:
var path = firebase.database().ref(our_firebase_data_url);
var fx = function fx_call(data) {
value = data.val();
console.log("Here is your passed parameter: " + i); // we want this defined
};
path.on('value', fx);
We thought we might be able to do something like this but we are unable to get variations on this to work:
fx_call(data,i) {
value = data.val();
i = i.val();
};
Alternatively, it looked like we could pass in our values via a .bind statement:
fx_call(data,i) {
value = data.val();
i = i.val();
}.bind(this, i) # we also tried .bind(null,i) and .bind(i)
But every approach we have tried from multiple SO posts (1,2,3 and others) resulted in a data.val() undefined error for us.
How can we pass in additional variable parameters to our promise's callback?
We found an answer outside of SO, so we are asking and answering to add it here.
You can pass variables into functions including Firebase promise callbacks using a .bind statement formatted this way:
fx_call(data,i) {
value = data.val();
i = this.i; # this is how you access the value from the bind
j = this.j;
}.bind( {i: i, j: 10} ) # this is how you pass variables into the callback function...this.i will be whatever value the scope of i was at the time the function was created...this.j in this case will be 10

Passing variable in parent scope to callback function

This is more of a JavaScript Closure question than a Firebase question. In the following code, the Firebase callback isn't recognizing the variable myArr in the parent scope.
function show_fb() {
var myArr = [];
var firebase = new Firebase('https://scorching-fire-6816.firebaseio.com/');
firebase.on('child_added', function(snapshot) {
var newPost = snapshot.val();
myArr.push(newPost.user);
console.log(myArr); // works
});
console.log(myArr); // doesn't work. myArr in the firebase.on callback is
// not altering myArr
return myArr;
};
The callback is recognizing/modifying myArr perfectly fine. The problem is that when your "doesn't work"-labeled console.log(myArr) executes, the callback hasn't fired yet.
Let's change your code a bit:
var myArr = [];
function show_fb() {
var firebase = new Firebase('https://scorching-fire-6816.firebaseio.com/');
firebase.on('child_added', on_post_added); // steps 1-3
console.log(myArr); // step 4
return myArr; // step 5
};
function on_post_added(snapshot) { // step 6
var newPost = snapshot.val();
myArr.push(newPost.user); // step 7
console.log(myArr); // step 8
}
Now it might be a bit easier to see what's going on.
You register a listener for child_added that will call on_post_added for every post that is added to your Firebase
This will result in a call to the server, which may take a significant amount of time to return
Meanwhile your JavaScript code continues and...
Logs the array, which at this stage is still empty
And then thus returns an empty array
Now at some point the server returns the new value(s) and your callback is invoked
Which means we can add it to the array without problems
And logging it to the console shows the expected values
Handling asynchronous code/callbacks like this takes some getting used to, but is crucial to working with Firebase or any other AJAX-like or event driven technology. Putting the callback's code into a separate function sometimes makes it a bit easier to see what's going on.
In the case of Firebase it may also help to realize that the event is called child_added for a reason. It is called whenever a child is added to the Firebase, not just when you first register your callback. So minutes later when some other client adds a child, your callback will still fire, adding a new child to myArr. At that stage the code in steps 4 and 5 above will long have executed and will not execute again.
The solution is simple: put anything that you want to do after a child is added into your callback:
var myArr = [];
function show_fb() {
var firebase = new Firebase('https://scorching-fire-6816.firebaseio.com/');
firebase.on('child_added', on_post_added);
};
function on_post_added(snapshot) {
var newPost = snapshot.val();
myArr.push(newPost.user);
console.log(myArr);
// do whatever else you need to do for a new post
}
The child_added event is not immediately executed, therefore is not synchronous and you can't rely on it to have executed, before the log call at the end of your function.
The procedure is:
Define myArr
Instantiate Firebase
Assign event handler for child_added
Log the value of myArr
Return myArr - end of function
Now at some point after this, the child_added event is fired, which pushes to your array, but as you can see, your show_fb() function has already finished executing by this point.
If Firebase makes ajax call(it probably does), then callback function(snapshot){..} is called after return statement. So function show_fb always returns [].
for instance:
You execute this statement: var x=show_fb();
show_fb creates empty array
function creates ajax call
function returns myArr (it is empty at this moment)
variable x gets reference to myArr (array is still empty)
callback is called and inserts new value to x (x and myArr have same instance)

trying to store all objects into one object with parse and jquery

I'm trying to store all returned objects into one object for later use. I have the loop set in place but when I try to execute some code with the new object, its empty. In this case here alert the object. I guess the alert is executing before the loop is complete. Any way to fix this?
var followers = new Array;
Parse.initialize("xxxxxx", "xxxxx");
var currentUser = Parse.User.current();
var users = JSON.stringify(currentUser);
var user = eval("(" + users + ")");
var listsfollow = user.Follow;
for (var i = 0; i < listsfollow.length; i++) {
var allUsers = Parse.Object.extend("User");
var query = new Parse.Query(allUsers);
query.get(listsfollow[i], {
success: function (results) {
followers.push(results);
},
error: function (object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and description.
}
});
};
alert(followers);
You are correct and the AJAX stuff in jquery is async, and so the .get() will happen after the alert. The quickest and dirtiest way would be to set it to an async to false before issue the request: http://api.jquery.com/jQuery.ajax/
A better way would be to have your code respond to the result of the call whenever it is ready. This will prevent page blocking and make your code faster.
This is an asynchronous query, and the alert is being shown right after the query has initiated, so it's unlikely that it will be executed after the query has returned any results.
Any code that depends on the query's results should be moved into either the success or error callback functions.
I couldn't figure out how to make it wait for the query but I used this to do something after the last loop iteration. Worked great.
if((--remaining)==0)alert(followers);

Accessing Object properties when the object is within an array (javascript)

I am attempting to create an array of objects and then access object properties within the array, but it comes back undefined. I call the createObjArray() function and immediately after I do a console.log(objArray[1]); and it prints out the object with all it...s properties just fine. However, if I attempt to do console.log(objArray[1].name); firebug prints "undefined". Also, when stepping through my code in firebug I can mouse over objArray[1].name and it displays the correct name. What is happening here, it's driving me nuts.
var objArray = [];
function createObjectArray(numOfObjs) {
for(var i=0; i<numOfObjs; i++) {
packages.push(initObj(i));
}
}
function initObj(i){
var newPackage;
var p = {};
$.getJSON('.../package' + i + '.json', function(data) {
newPackage = new Package(data);
p.name = newPackage.name;
p.id = i;
});
return p;
}
This will work:
var objArray = [];
function createObjectArray(numOfObjs, callback) {
var filledPackage = [];
var nbLeft = numOfObjs;
for(var i=0; i<numOfObjs; i++) {
initObj(i, function(p){
filledPackage.push(p);
nbLeft--;
if (nbLeft === 0){
callback(filledPackage);
}
});
}
}
function initObj(i, callback){
var newPackage;
var p = {};
$.getJSON('.../package' + i + '.json', function(data) {
newPackage = new Package(data);
p.name = newPackage.name;
p.id = i;
callback(p);
});
}
//Get a filled object array:
createObjectArray(5, function(filledArray){
objArray = filledArray;
//Code here will be executed AFTER all the $.getJSON queries have returned.
//objArray is not empty.
});
//Code here will be executed WHILE the getJSON queries are running and
//while objArray is still empty. Due to the way the JS event loop works,
//it is impossible that code placed here will be able to use the content
//of objArray unless you call an async function such as anything AJAX or
//setTimeout, but that's iffy. Code you want to be executed once objArray
//has been filled should be inside of the callback above.
The problem is that $.getJSON is aynchronous, meaning that it doesn't automatically returns a result. Instead, you give it a callback. A callback is a function to execute once it has received a result. In this case, the callback is the anonymous function created when calling $.getJSON. That callback receives the result from the server, adds it to the array and then checks if the array has been filled. Since we're doing async code due to the $.getJSON function, we must return the result asynchronously too. To do so, we demand the initObj function to receive a function to call once it has completed (another callback). We call that callback and pass it the parameter. We then return the filled array through a callback once again.
Your call to $.getJSON is asynchronous. When initObj() returns p it is still an empty object.
However initObj() creates a closure which captures a reference to p so when $.getJSON returns p is populated.
This is why the object seems empty in code you run immediately after populating the array. However by the time you run your console command the asynchronous calls have returned and the objects are populated.
You need to wait for all your async calls to return before continuing work on the array. One way to do this would be to increment a counter when you make a call and decrement it when a call returns, then when the final call returns the counter would drop to zero and you continue processing.
Alternatively you could setup a setTimout loop to keep polling the array the check when all its items are populated.
Both approaches are risky if you think one of the calls might fail, but the approach itself is fundamentally risky as you are making multiple ajax calls so you have to handle multiple possible failures. It would be a lot cleaner to grab all the data in one go so you can handle success / error states once in the success / error handler in jQuery.ajax.

Categories