I am trying to store the text that I retrieve from my firebase database, but when I assign datasnapshot.val() to a variable, it seems to have no effect.
var ref = firebase.database().ref().child("Text");
var output = "initial";
ref.on('value',function(datasnapshot){
output = datasnapshot.val();
})
console.log(output);
The console.log still outputs "initial". Why is this?
Firebase APIs are asynchronous, meaning queries for data, (the on() method in your case) will return immediately, and the results will be sent to you callback some time later. Your code is expecting to receive the results immediately, which will not happen. The log line is being executed immediately after the on() method returns, before the results are available to the callback.
Instead of trying to use the results outside the callback, you should fully handle them inside the callback, where they first become available:
ref.on('value',function(datasnapshot){
output = datasnapshot.val();
console.log(output);
})
Please also read this blog post that goes into more detail about Firebase asynchronous APIs, and why they were designed like that.
Related
I am trying to access the rate variable from outside the function in which it is defined. I have declared a global rate variable but it is "undefined" at the point I need to use it. I've minimized the code to keep it as clean as possible.
var db = firebase.firestore();
var docRef = db.collection("pricing").doc("default");
var rate; //Setting up global variable to store value
docRef.get().then(function(doc) {
console.log("Document data:", doc.data().base); //This works
rate = doc.data().base;
console.log("Rate:", rate); //This works
});
console.log("Rate:", rate); //rate is "undefined" but rate variable value
//is accessible if typed via the console?
docRef.get() is asynchronous and returns immediately, before the query is complete. You use that promise to attach a callback function using then that gets invoked some time later. Meanwhile, your code will continue to execute. What you are observing is that the console log is running before rate gets defined in the callback.
You will have to adjust things so that your code that depends on rate being defined must only execute after the callback. This typically means that you write the code inside the callback, or using another callback.
It's strongly advisable to learn how JavaScript promises work, as they are critical to writing effective code.
rate is being assigned inside then block, which is executed when Promise, returned from docRef.get() will be resolved. So at the moment of calling console.log, as of then block executes asynchronously, rate is not assigned yet.
It's a race condition: rate is assigned when the promise returns--after your console.log.
You could set your code up to run synchronously; then, the console.log will not run until after the data have been returned:
Firebase Firestore retrieve data Synchronously/without callbacks
Note that this is generally not recommended, as it can negatively affect performance and UX. Pay close attention, and you may notice a short lock-up of the interface. Regardless, per your OP, since you are a "newbie," this may help you here for learning and testing.
I'm quite new to using Javascript and particularly JSON, and I've been struggling to do this:
There is a JSON file on my web server which I am trying to access and parse into a JavaScript object. What I am trying to do is parse that JSON into an array and then further manipulate that array based on other user variables.
This is a sample of what the JSON looks like:
{"log":
[{
"name":"Al",
"entries":[8,12,16,19]},
{"name":"Steve",
"entries":[11,17,22]}]}
What I need to do is retrieve the array for one of the entries and store it in an array as a JavaScript object. What I have tried to do is this:
var entriesLogged;
fetch ('url to the json file').then(function(response){
return response.json();
}).then(function(data){
entriesLogged = data.log[0].entries;
});
However, I can't seem to get this to work and to assign the value to the variable in a way that persists outside of this scope. I have been able to output the value of the array using console.log, but I have not been able to actually work with and manipulate that data like an object. I'd ideally like to parse the JSON file from the server onto a global array.
Most of the tutorials I've come across so far have used the JSON file to output console logs or change the contents of html elements, however I need to retrieve the values from the JSON into a global array first.
Am I missing something here? Does anyone have advice on how to do this?
Best wishes,
Dom
Are you trying to manipulate the entriesLogged variable after the fetch promise chain? The fetch is asynchronous so this means that any code after the fetch chain will run before the fetch chain finishes.
var entriesLogged;
fetch('url to the json file').then(function(response){
return response.json();
}).then(function(data){
entriesLogged = data.log[0].entries;
});
// Code down here will run before the fetch chain finishes
// So if you're using entriesLogged down here, it will be null
console.log(entriesLogged); // entriesLogged is null
You might want to do something like:
.then(function(data){
entriesLogged = data.log[0].entries;
myFunction(entriesLogged);
});
function myFunction(myData) {
console.log(myData); // can access entriesLogged here
}
What is happening is that the call is async, it means that when you make that call a new thread is created and javascript automatically passes to the next line.
var entriesLogged;
fecth(something).then(function(data){entriesLogged = data[0]}); // this goes to another thread an takes e.g. 5 miliseconds but javascript does not wait for this to complete, it goes to the next line
console.log(entriesLogged); // this is executed before the call is finished
There are TONS of "answers" for variations of this question that don't actually solve the problem, they just describe the issue. (tl;dr solution is last block of code)
The issue is that .fetch('URL.json') and .json() are "asynchronous" calls. So the script will start working on that call, then continue on to the rest of the commands in your script while the .fetch() and .json() calls are still being worked on. If you then hit a command BEFORE one of these asynchronous calls is done, the rest of your script will have no data to work with.
There are tons of places to look for fully understanding the ins and outs of how async calls work in JS, for example here:
How do I return the response from an asynchronous call?
If you're like me and you just want the code to function synchronously, the quick and dirty solution is to create an async main loop and then to make sure you use the await keyword when calling asynchronous functions that you need to WAIT for the data to be populated on.
Example main loop:
(async () => {
// put main loop code here
})();
You would then put your code inside that main loop BUT you have to make sure that you're adding the await keyword in front of every asynchronous call you use. in the original example provided, you'll have to declare your .then() functions that need to use await as async as well. This is confusing if you don't fully understand the asynchronous nature of javascript and I'll simplify it later, but I wanted to provide a working version of the original code provided so you can understand what changed:
(async () => {
var entriesLogged;
await fetch('url_to_the_file.json')
.then( async function(response){
return await response.json();
})
.then( function(data){
entriesLogged = data;
});
})();
The .then() call is typically used for post-processing so you can do work inside of the asynchronous call... but since in this example we're deliberately making it synchronous, we can avoid all of that and clean up the code by doing individual simple commands:
// Start main loop
(async () => {
let response = await fetch('url_to_the_file.json');
let entriesLogged = await response.json();
})();
That should get you what you're looking for and hopefully save anyone like us the heartache of spending hours of time trying track down something that there is a simple solution for.
Also want to give a huge call out to the place I got the solution for this:
https://www.sitepoint.com/delay-sleep-pause-wait/
Im trying to assign variables to their respected value from the firestore database using the get doc function, I've noticed it does not assign or update the values what so ever.
I've tried to work with async and awaits but cannot seem to make it work.
getFromDatabase(nameOfCollection,nameOfDocument){
const db = firebase.firestore();
var docRef = db.collection(nameOfCollection).doc(nameOfDocument);
docRef.get().then(function(doc) {
if (doc.exists) {
outvariable = doc.data().anyfield; // THIS IS WHAT I WANT
console.log(" Document data:", doc.data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
}
im expecting outvariable = doc.data().anyfield
Most likely you're confused by the fact that data is loaded from Firestore asynchronously. It's not so much that the data isn't assigned to the values, because it really is. It just happens at a different time than you expect.
It's easiest to see this by adding some simple logging statements around the code that loads data:
const db = firebase.firestore();
var docRef = db.collection(nameOfCollection).doc(nameOfDocument);
console.log("Before starting to load data");
docRef.get().then(function(doc) {
console.log("Got data";
});
console.log("After starting to load data");
When you run this code, the output is:
Before starting to load data
After starting to load data
Got data
This is probably not what you expected, but it's actually completely correct. The data is loaded from Firestore asynchronously (since it may take some time), and instead of waiting, the main code continues. Then when the data is available, your callback function is called with that data.
This means that any code that requires the data from the database must be inside the callback, or be called from there. So the console.log(" Document data:", doc.data()) in your original code should work fine. But a similar console.log outside of the callback won't work, because it runs before the data is available.
This is an extremely common source of confusion for developers new to this type of API. But since most modern web/cloud APIs, and many other APIs, are asynchronous, it's best to learn how to work with them quickly. For that, I recommend reading:
get asynchronous value from firebase firestore reference
Doug's blog post on why Firebase APIs are synchronous
Firestore query in function return
NodeJS, Firestore get field
How do I return the response from an asynchronous call?
The data can be extracted with .data() or .get() to get a specific field.
For example: doc.get(anyfield);
More info can be found on the official documentation.
if I have a code like this
if (request.params.friends != null)
{
_.each(request.params.friends, function(friend) {
// create news
var News = Parse.Object.extend("News");
var news = new News();
news.set("type", "ask");
news.save();
});
response.success();
}
and the length of request.params.friends is 2, does the second news get saved for certain? If not, how to make sure it gets saved? I looked at Parse.Promise documentation and in all the examples, the loop is inside a query or a save. Do I need to save the first news first and then create the Promise? I still don't get how "asynchronous" works.. Does the response.success() work like a return or break?
The loop does get executed twice.
response.success() acts like a return.
The asynchronous magic is in the "save" method. When "save" is called, the Parse.com says, "ok, you want me to save it. I'll save it, but not now. For now, here is a promise that I'll save it later." The save method returns an Promise object and the promise will be fulfilled when the object is actually saved.
So what happens is a little like
First time through the loop: create friend #1.
Ask Parse to save friend #1.
Second time through the loop: create friend #2.
Ask Parse to save friend #2.
Return successful response.
Parse actually saves friend #1 and friend #2
It's been a while since I've used Parse, but I'm not sure usually both the friend objects would actually get saves. Calling response.success() could kill work-in-progress. Here is an alternative implementation:
var objectsToSave = _.collect(request.params.friends, function(friend) {
var news = new News();
news.set({type : "ask"});
return news;
});
Parse.Object.saveAll(objectsToSave, {
success: function(list) {
// All the objects were saved.
response.success();
},
error: function(error) {
// An error occurred while saving one of the objects.
},
});
The saveAll function saves all the objects at once. It's usually faster than saving objects one-at-a-time. In addition to providing saveAll with the objects to save, we provide it an object with a success function and an error function. Parse.com promises only to call the functions AFTER the save is complete (or it experienced an error).
There are a few other things going on. The Parse.Object.extend statement belongs in a different place in your code. Also, the set function doesn't take a list of strings. It takes a JavaScript object.
I was watching this AngularJS tutorial describing how to hook into Twitter with an Angular resource. (Video tutorial) Here is the resource that is set up in the example controller:
$scope.twitter = $resource('http://twitter.com/:action',
{action: 'search.json', q: 'angularjs', callback: 'JSON_CALLBACK'},
{get: {method: 'JSONP'}});
The tutorial shows that there are a couple ways to get data back from the resource using the get call. The first method is by passing a callback to the get function. The callback will be called with the result after the ajax request returns:
$scope.twitter.get(function(result) {
console.log('This was the result:', result);
});
I understand this method. It makes perfect sense to me. The resource represents a place on the web where you can get data, and get simply makes an ajax call to a url, gets json back, and calls the callback function with the json. The result param is that json.
It makes sense to me because it seems obvious that this is an asynchronous call. That is, under the hood, the ajax call fires, and the code following the call isn't blocked, it continues to be executed. Then at some indeterminate point later on, when the xhr is successful, the callback function is called.
Then the tutorial shows a different method that looks a lot simpler, but I don't understand how it works:
$scope.twitterResult = $scope.twitter.get();
I assume that the xhr underneath get must be asynchronous, yet in this line we are assigning the return value of the get call to a variable, as if it returned synchronously.
Am I wrong for not understanding this? How is that possible? I think it's really neat that it works, I just don't get it.
I understand that get can return something while the xhr underneath it goes off and processes asynchronously, but if you follow the code example yourself, you will see that $scope.twitterResult gets the actual twitter content before any subsequent lines are executed. For example, if you write console.log($scope.twitterResult) immediately after that line, you will see the results from twitter logged in the console, not a temporary value that is replaced later on.
More importantly, because this is possible, how can I write an Angular service that takes advantage of this same functionality? Besides ajax requests, there are other types of data stores requiring asynchronous calls that can be used in JavaScript which I would love to be able to write code for synchronously in this style. For example, IndexedDB. If I could wrap my head around how Angular's built-in resources are doing it, I would give it a shot.
$resource is not synchronous although this syntax might suggest that it is:
$scope.twitterResult = $scope.twitter.get();
What is going on here is that call to the AngularJS will, after call to twitter.get(), return immediately with the result being an empty array. Then, when the async call is finished and real data arrives from the server, the array will get updated with data. AngularJS will simply keep a reference to an array returned and will fill it in when data are available.
Here is the fragment of $resource implementation where the "magic" happens: https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L372
This is described in the $resource documentation as well:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most case one never has to write a callback function for the action methods.
$q can do this trick too. You can convert a normal object to a 'delayed value' using something like this:
var delayedValue = function($scope, deferred, value) {
setTimeout(function() {
$scope.$apply(function () {
deferred.resolve(value);
});
}, 1000);
return deferred.promise;
};
and then use it in a controller, to get a similar effect to what $scope.twitter.get() does in the OP's example
angular.module('someApp', [])
.controller('someController', ['$scope', '$q', function($scope, $q) {
var deferred = $q.defer();
$scope.numbers = delayedValue($scope, deferred, ['some', 'numbers']);
}]);