Get the current state in a transaction operation in Firebase - javascript

I have created a script that performs a request with firebase. Since I want to avoid multiple queries on a relation at the same time, I use the transaction function as seen in the code. First, I get the current status of the relation and check if it is present or not. This seems to work. The first time is const current = null and the function to add value to data2 will be executed.
And if I run the script the second time, then he goes into the next function successfully (doSecondFunction()).
And unfortunately, no currentData returns to me here. I expect the value of the ref data2 that was added the first time. But as a result, I get null.
Is there a way to work in the transaction with the current state of the database?
const ref = firebase.database().ref('data2');
ref.once('value', function(snapshot) {
const current = snapshot.val();
if(current == null){
console.log('FIRST');
addValToRefFunction();
}else{
console.log('SECOND');
doSecondFunction(current, ref);
}
});
function doSecondFunction(current, ref){
ref.transaction(function(currentData){
console.log(current); // result is the right value
console.log(currentData); // result is null ?
if (currentData === current){
console.log('Check');
return;
}
}

There is no way control what the client sees as the current status of a node when starting a transaction. In my experience the first time the callback gets invoked, the value is almost always null, and the callback needs to handle that.
Also see my answer here for a description of the process: Firebase runTransaction not working - MutableData is null

Related

Seeking proper way to use the get() method of INDEXEDDB data store

I am attempting to use a simple indexeddb 'get' method to retrieve a keyed record from an existing data store. For some reason that i have been unable to find, though the result of my 'get' request is 'successful', the returned value is 'undefined'.
The code below is called from an event listener that is fired when the user clicks on a button in a row of a table, indicating the user wants details on that particular row. Each row in the table represents a record in the indexeddb data store that has already been created. There are 8 values of interest in each row, but only 3 are displayed in my html table. I am simply attempting to access the row in the data store to get all 8 values in order to pass them along to the next process.
The following code is called from an event listener created on a 'Select' button for each row...
async function getGameInProgressDetails(gameID) {
try {
db = await idbConnect(DBName,DBVersion);
let tx = db.transaction(['gamesList'], 'readonly');
let gameStore = tx.objectStore('gamesList');
// I have confirmed the 'gameID' passed in is the key value that i would expect
// to retrieve the desired result.
let req = gameStore.get(gameID); // gameID comes from the selected row in the html table.
req.onsuccess = (ev) => {
console.log('Success');
let request = ev.target;
console.log(request); // returns an IDBRequest object with result: undefined and error: null.
let theGame = request.result;
console.log(theGame ); // displays 'undefined'.
}
req.onerror = (err) => {
console.warn(err);
};
} catch (e) {
console.warn(e);
}
I get the 'success' message indicating that the 'get' operation was successful, but my result is 'undefined'. Could someone please tell me if i am missing a step or if there's another way to go about this that i should look into? I've looked at a variety of tutorials and i must be overlooking something 'obvious'.
I discovered the problem with my code/approach and am answering my own question. The reason that the above code was not working was not because of my indexeddb code or logic at all.
What i discovered was that the index value that i had passed into the routine needed to be cast as an integer before the get() method call, JS was treating it as a string value. I was confused by the fact that i had checked the value in a console.log statement and it had shown the appropriate value. What i hadn't considered was how JS evaluated what type the variable value was.
For those coming later and seeing this, my final code was thus:
async function getGameInProgressDetails(gameID) {
db = await idbConnect(DBName,DBVersion);
let tx = db.transaction(['gamesList'], 'readonly');
var gameStore = tx.objectStore('gamesList');
let gameIndex = gameStore.index("gameIDIdx");
let request = gameIndex.get(parseInt(gameID)); //!! NOTE THE parseInt!!
request.onsuccess = function() {
if (request.result !== undefined) {
console.log("Games", request.result);
} else {
console.log("No such games.");
}
}

sendbird createMyGroupChannelListQuery return empty when recall

I'm using SendBird Javascript SDK createMyGroupChannelListQuery() and the instance's next() method to retrieve the list of group channels. However, it will return the list only one time after initialized the instance, the next time it gets called, it result is always an empty array. Since I need to fetch the channels multiple times I need to have the full list of channels always. Please let me know if you have experienced this.
// Retrieve a list of channels
var listQuery = sb.GroupChannel.createMyGroupChannelListQuery();
listQuery.includeEmpty = true;
listQuery.order = 'latest_last_message';
listQuery.limit = 100; // The value of pagination limit could be set up to 100.
if (listQuery.hasNext) {
listQuery.next(function(groupChannels, error) {
if (error) {
// Handle error.
}
// A list of group channels is successfully retrieved.
groupChannels.forEach(channel => {
...
});
...
});
}
To call it again and get the result, just call:
listQuery.hasNext = true;
I have a sandbox for you to try:
https://codesandbox.io/s/javascript-has-next-problem-fducb?file=/src/index.js
Change APP_ID and USER_ID
Check the console, it should appear "First time" and the list of channels and then "Second time" and again the list of channels.

How can I set data from firestore to a global variable

I am trying to fetch data from firestore and set it to a global variable but I'm getting undefined. Here is what I have;
let myScore;
auth.onAuthStateChanged((user) => {
let docRef = db.collection("players");
let user_email = user.email;
docRef.onSnapshot((players) => {
players.forEach((doc) => {
data = doc.data();
if (user_email == data.email) {
myScore = data.endless_score;
console.log(myScore); //This log the right score
}
});
});
});
console.log(myScore); // This logs undefined
How can I get myScore i.e. the second console log to output the score from firestore?
Okay, from what I see you are trying to get the current users when the user logs in but I think attaching a snapshot listener is not the optimal solution. Because first of it would count as a read for every player document there is. What I suggest you to do is this :
const myUser = (await (await firebase.firestore().collection("players").where("email","==",user.email).limit(1).get()).docs[0].data()
This Will Give You Your Current User's data then you can use it however you want by doing myUser.endless_score. It will also reduce the cost because you are only getting a single document where email is equal to current users email
also don't forget to make your arrow function async in order to use await :)
Calling onSnapshot creates a background-call to firestore. It run independently of the rest of your code. So your second console log is practically called immediatly after let myScore, meaning it's still undefined.
As well, onAuthChanged is an observer. It can be triggered at random, depending on when you sign in/out of firestore.
To the functionality you want, sort of, you'd need to rewrite the code to use async/await.
Defining a function() as 'async' and then calling 'await function()' in your code will make the code literally wait for the online call to finish before continuing.
read:
https://javascript.info/async-await

Right way or method Do While If Condition set inside loop?

I can't figure out the right way to do that:
I'm calling external API for products and I get a response I want to add those products to my database and if the response contains a next_page url loop it again until there is no next_page left
Here is what I came up with:
var products = api.ProductsActive('GET', {includes: 'Images', limit: 1});
requestProducts = function(){
products.results.forEach(function(product){
var sameproduct = apiProducts.findOne({listing_id: product.listing_id});
if (sameproduct) {
console.log('found sameproduct');
return;
}
//Add userId to current product so we can assosicate products belong to "X" user
var productExtend = _.extend(product, {userId: Meteor.userId()});
apiProducts.insert(productExtend);
});
//If there is next page get the next page number and get products
var nextPage = products.pagination.next_page;
if (nextPage !== null) {
products = api.ProductsActive('GET', {includes: 'Images', page: nextPage, limit: 1});
console.log(products);
}
};
//loop at least once, and then if more pages found
do {
requestProducts();
}
while (products.pagination.next_page !== null);
Which doesn't seems to work right I'm not sure if this is the right method for such function I really help your input! Please help me figure out the right way for this!
Keep in mind javascript is asynchronous, so you need to handle your loop with callbacks instead of do. The reason for this is requestProducts likely uses a callback and the javascript interpreter doesn't "wait" for requestProducts() to finish running before it runs the next line of code.
You haven't provided any details of how the api works so I have to generalise.
If requestProducts() takes a parameter as a callback (which it should), use that to re-run itself:
var callback = function(err, result) {
if(products.pagination.next_page !== null) requestProducts(callback);
};
requestProducts(callback);
You would have to check the your api's documentation to check how the callback is defined but it would be something like the above where err is returned if there is a problem.
How this works is, when requestProducts is called the callback fires and if its not the last page to repeat itself until ..next_page == null

Firebase field delayed update

I have a Firebase with a users reference, which has a field user_id.
([Firebase]/[app]/users/user_id)
I insert a user & assign a user_id. However, when I read the value of user_id from userRef (a reference to users), it does not read it the first time. Subsequent reads work perfectly fine.
Using the debugger, I can see that when the user is created, a user_id is assigned. It seems like my first call refreshes the Firebase reference, so that subsequent calls are now seeing the udpated Firebase (I don't think that is what it is - that is just how it appears).
This is my code to read the value of user_id:
var userID = 0;
userRef.on('value', function(snapshot) {
userID = snapshot.val().user_id;
});
alert(userID);
The first time, the alert shows 0. Every time after that, it shows the correct value.
To make the problem even stranger, I also have a games reference with a game_id, created in the same way as user. But my read on game_id (in the exact same way & at the same time) works every time.
Any ideas?
The issue here is that .on() doesn't (in general) trigger your callback function immediately. In particular, the first time you do a .on() at a location, Firebase has to go ask the server for the current value, wait for the response, and THEN call your callback. And it does this all asynchronously.
The way your code is currently written, "alert(userID);" is being run before your callback code ("userID = snapshot.val().user_id;") so it always reports 0 the first time. The simple fix is to move the alert() inside your callback:
var userID = 0;
userRef.on('value', function(snapshot) {
userID = snapshot.val().user_id;
alert(userID);
});
Here's a common methodology to wait on two callbacks, using using jQuery's Promise model and once:
var userID = 0;
// sends a callback to fx where the results can be stored
function defer( fx ) {
return function() {
var deferred = $.Deferred();
fx( function(snapshot) { deferred.resolve(snapshot.val(); } );
return deferred.promise();
}
}
$.when( // wait for both actions to complete
defer( function(callback) { userRef.once('value', callback) }),
defer( function(callback) { widgetRef.once('value', callback) })
).then( function(values) {
// both deferreds are done now
// and values contains an array with the snapshot.val() from each call
console.log(values);
});

Categories

Resources