HTML: How to get a calculation result before executing a function - javascript

I have a script that I am trying to execute. I need the script to, on the press of the submit button:
fetch values from Google firestore
Use those values in a calculation
use the result of this calculation to write back to the firestore database
I have put console.log("eff[1,2,3]" + result of calculation) to help trace in the console log the order of execution. It first displays eff3 then eff1 then eff2 so this means that the value submitted to firestore is zero (as it is before the calculation) The calculation does calculate the correct answer it just doesn't submit the answer to the database.
How do I change my code to have it execute in the right order?
// Triggered when the send new message form is submitted.
function onMessageFormSubmit(e) {
e.preventDefault();
//Calculate efficiency
var preodo = 0
var efficiency = 0
firebase.firestore().collection('Vehicle').doc(reg.value).collection('Refuels').orderBy('Date', 'desc').limit(1)
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
preodo = change.doc.data().Odometer
efficiency = (odo.value - preodo) / amount.value
efficiency = efficiency.toFixed(4);
console.log("eff1:" + efficiency)
});
console.log("eff2:" + efficiency)
});
//Save refuel transaction with all details form the form as well as the calculated efficiency
console.log("eff3:" + efficiency)
saveMessage(reg.value, odo.value, date.value, amount.value, price.value, rebate.value, efficiency).then(function() {
window.alert('Refuel submitted!')
// Clear message text field and re-enable the SEND button.
resetMaterialTextfield();
toggleButton();
});
}

Firestore operations are asynchronous, so you'll need to handle them with a promise. Do exactly what you did with saveMessage(), but with returning a value
Also, use .get() instead of .onSnapshot(), as the latter will continue to listen to changes made to the referenced document, which you don't need because you do this operations when a button is clicked.
function get_efficiency(regValue) {
efficiency = -1
// return a Promise which will return the efficiency after all computations are done
return firebase.firestore().collection('Vehicle')
.doc(reg.value)
.collection('Refuels')
.orderBy('Date', 'desc')
.limit(1)
.get()
.then(function (querySnapshots) { // this will execute after all documents are fetched from the db
querySnapshots.forEach(function(doc) {
preodo = doc.data().Odometer
efficiency = (odo.value - preodo) / amount.value
efficiency = efficiency.toFixed(4);
console.log("eff1:" + efficiency)
});
console.log("eff2:" + efficiency)
return efficiency
})
}
// Triggered when the send new message form is submitted.
function onMessageFormSubmit(e) {
e.preventDefault();
//Calculate efficiency
var preodo = 0
var efficiency = 0
get_efficiency(reg.value)
.then(function (efficiency) { // this will execute after you computed the efficiency
// efficiency param is the one returned from the PROMISE from get_efficiency()
//Save refuel transaction with all details form the form as well as the calculated efficiency
console.log("eff3:" + efficiency)
saveMessage(reg.value, odo.value, date.value, amount.value, price.value, rebate.value, efficiency).then(function() {
window.alert('Refuel submitted!')
// Clear message text field and re-enable the SEND button.
resetMaterialTextfield();
toggleButton();
});
})
}

By using #Professor Abronsius' suggestion, I put my code in a Promise
// Triggered when the send new message form is submitted.
function onMessageFormSubmit(e) {
e.preventDefault();
let myPromise = new Promise(function(myResolve, myReject) {
//Calculate efficiency
var preodo = 0
var efficiency = 0
firebase.firestore().collection('Vehicle').doc(reg.value).collection('Refuels').orderBy('Date', 'desc').limit(1)
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
preodo = change.doc.data().Odometer
efficiency = (odo.value - preodo) / amount.value
efficiency = efficiency.toFixed(4);
console.log("eff1:" + efficiency)
});
console.log("eff2:" + efficiency)
myResolve(efficiency); // when successful
myReject("Not able to calculate efficiency"); // when error
});
});
// "Consuming Code" (Must wait for a fulfilled Promise)
myPromise.then(
function(efficiency) {
//Save refuel transaction with all details form the form as well as the calculated efficiency
console.log("eff3:" + efficiency)
saveMessage(reg.value, odo.value, date.value, amount.value, price.value, rebate.value, efficiency).then(function() {
window.alert('Refuel submitted!')
// Clear message text field and re-enable the SEND button.
resetMaterialTextfield();
toggleButton();
});
},
function(error) {
window.alert(error)
}
);
}
This works... It submits to the firestore only once but... it prints to the console log as follows:
eff1, eff2, eff3, eff1, eff1, eff2.
I'm now curious as to why it repeats itself? It doesn't really matter that it repeats this because it doesn't affect the database or the user interface but I am curious.

Related

Sending a message in smaller chunks

I am using DiscordJS and their API has a character limit and will reject message if limit is exceeded.
Through fetchData()I am successfully building assignedPrint, which is an array of messages that I would like to send over the API.
So I already have the array ready to go but I am also using an auto update feature (courtesy of WOK) where every set amount of time, array would be flushed refilled with fresh data and sent over again for editing the original message through the message.edit() method.
It works just fine but I am forseeing that my array might get bigger over time and sending a single message may break things because of the API max character limit.
const getText = () => {
return assignedPrint
+ greengo + 'Updating in ' + counter + 's...' + greenstop;
};
const updateCounter = async (message) => {
message.edit(getText());
counter -= seconds;
if (counter <= 0) {
counter = startingCounter;
// emptying the array before fetching again for the message edit
assignedPrint = [];
await fetchData();
}
setTimeout(() => {
updateCounter(message);
}, 1000 * seconds);
};
module.exports = async (bot) => {
await fetchData();
const message = await bot.channels.cache.get(tgt).send(getText());
updateCounter(message);
};
As you can see, the hurdle is that getText()includes everything.
I tried sending one element as a time using for (cont e of assignedPrint) and it worked but how can I edit every message upon refreshing data, knowing that new data may be added or some already sent data could be removed...
The easiest of course is to do it in one single message but again, it may hit the quota and cause a crash.
Thanks.

How to make dialogflow going while waiting for a process that takes more than the time limit?

I have created an API that takes user input and process it. However, the process takes more than 5 seconds (dialogflow limit).
How can I continue with other processes until this certain process is finished?
Or is it possible to return to the user any messages like "Please hold on a bit..." so it can restart the time.
var message = "hi"; //test purpose
async function GetCertain_info(agent) {
await uiFx();
agent.add("Here are the information on " + message);
}
async function uiFx() {
var {
ui
} = require('./uia.js');
return new Promise(function(resolve, reject) {
ui().then((msg) => {
console.log("Destination Message : " + msg)
message = msg;
resolve(message);
}).catch((msg) => {
console.log(msg)
message = msg;
reject(message);
})
});
}
Appreciate your help
Yes, it is possible to return to the user any messages like "Please hold on a bitā€¦" by setting up a FollowupEvent.
You can extend the 5 seconds Intent limit up to 15 seconds by setting up multiple follow-up events. Currently, you can only set up 3 follow-up events one after another (which can extend the timeout up to 15 seconds).
Here's an example of how you can do it in the fulfillment:
function function1(agent){
//This function handles your intent fulfillment
//you can initialize your db query here.
//When data is found, store it in a separate table for quick search
//get current date
var currentTime = new Date().getTime();
while (currentTime + 4500 >= new Date().getTime()) {
/*waits for 4.5 seconds
You can check every second if data is available in the database
if not, call the next follow up event and do the
same while loop in the next follow-up event
(up to 3 follow up events)
*/
/*
if(date.found){
agent.add('your data here');//Returns response to user
}
*/
}
//add a follow-up event
agent.setFollowupEvent('customEvent1');
//add a default response (in case there's a problem with the follow-up event)
agent.add("This is function1");
}
let intentMap = new Map();
intentMap.set('Your intent name here', function1);;
agent.handleRequest(intentMap);

force JQuery $(document).ready to wait for promise to resolve

So I am having some trouble assigning a variable to the response of a fetch request. I essentially want to run a fetch request to my API, which will return a value. I then want to assign that value to an element within the html.
When I run what I have, I can see that the value is being assigned as PromiseĀ {<pending>}
A few solutions I have seen is that I could run multiple $(document).ready(function(){});'s but I think it is still not resolving the promise before moving onto the next $(document).ready(function(){});
Here is what I have so far
var bal;
$(document).ready(function(){
function getCookie(name){
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if(parts.length == 2) return parts.pop().split(";").shift();
}
var decoded = jwt_decode(getCookie("JWT"));
console.log(decoded.email);
const url = "http://localhost:3001/wallets/" + decoded.email;
bal = fetch(url).then((resp) => {
return resp.json();
}).then((data) => {
console.log(data[0].balance);
return data[0].balance;
}).catch((error) => {
console.log(error);
});
$(document).trigger("my-event");
});
$(document).on("my-event", function(){
console.log(bal);
console.log(parseFloat(bal));
var oMain = new CMain({
win_occurrence:30, //WIN PERCENTAGE.SET A VALUE FROM 0 TO 100.
slot_cash: 100, //THIS IS THE CURRENT SLOT CASH AMOUNT. THE GAME CHECKS IF THERE IS AVAILABLE CASH FOR WINNINGS.
min_reel_loop:0, //NUMBER OF REEL LOOPS BEFORE SLOT STOPS
reel_delay: 6, //NUMBER OF FRAMES TO DELAY THE REELS THAT START AFTER THE FIRST ONE
time_show_win:2000, //DURATION IN MILLISECONDS OF THE WINNING COMBO SHOWING
time_show_all_wins: 2000, //DURATION IN MILLISECONDS OF ALL WINNING COMBO
money:bal, //STARING CREDIT FOR THE USER
...
I am still a little confused about how promises work and how to forcefully make things wait for them to resolve.
Just to provide an update and to close this question. This is the method I ended up with to resolve the issue.
const finishedPromise = fetch(url).then(r=r.json()).then(data => {
return data[0].balance'
});
Then within the function where I assign the value I simply used
money: await finsihedPromise

firebase transaction and if-then-else behaviour

I have some changes in my requirements:
Not only Create/Request/Cancel an entire Offer but do some actions on Offer's details:
Here is an offer in the activeOffers list:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 2
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a" ---- offering user
A user should be able to 'ask for seats' and if it's successful the Offer record should look like this:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 1 ----- reduced count
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a"
deals
-GHFFJ0vCnt9p8345b ----- the userId of asking user
seatsCount: 1
status: "asked"
But I have 3 problems after executing the source shown below:
(as shown above offer has 2 seats and a user asks for 1 seat)
After execution in my log I have BOTH "Reducing seats count by 1" and "Not enought seats"... i.e: the 'then' and 'else' part of 'if-then-else' :o
function result is [] - i.e. no deal created.
I'm not sure how to do the TODO: part - to add child (the new deal object) under dealsRef using asking userId as KEY because I think I don't need an autogenerated key here.
input data has the following structure:
data
"uid": "-GHFFJ0vCnt9p8345b", ----- the userId of asking user
"id": "-LKohyZ58cnzn0vCnt9p", ----- the id of offer
"details":
"seatsCount": 1
And here is my code:
dealSeats = function(data) {
const TAG = '[dealSeats]: ';
var details = data.details;
var info = data.info;
var entryRef = db.ref('activeOffers/' + data.id);
var entryDetailsRef = entryRef.child('details');
var seatsCountRef = entryDetailsRef.child('seatsCount');
var success = false;
return seatsCountRef.transaction((current)=>{
var value = current;
if (value >= details.seatsCount) {
success = true;
value = value - details.seatsCount;
console.log(TAG + 'Reducing seats count by ' + details.seatsCount);
} else {
console.log(TAG + 'Not enought seats');
}
return value;
})
.then(()=>{
var deal = [];
if (success) {
console.log(TAG + 'Succes');
deal.seatsCount = details.seatsCount;
deal.status = 'asked';
// TODO: here should add the new deal to dealsRef
return deal;
} else {
console.log(TAG + 'Failure');
return deal;
}
})
}
And as you can see - I'm not sure what is the right way to check if transaction is succeeded...
The reference documentation for DatabaseReference.transaction says:
... until your write succeeds without conflict or you abort the transaction by not returning a value from your update function.
So the way to abort the transaction is by not returning any value from your update function. That means the entire first block can be simplified to:
seatsCountRef.transaction((current)=>{
if (current >= details.seatsCount) {
return value - details.seatsCount;
}
})
Now it either returns the new value, or it returns nothing. The latter will then make Firebase abort the transaction.
To detect the final output of a transaction, I find it easiest to work with a completion callback (instead of a Promise), since it gives you all parameters in one call:
seatsCountRef.transaction((current)=>{
if (current >= details.seatsCount) {
return value - details.seatsCount;
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction, because there are not enough seats.');
} else {
console.log('Seat count updated');
}
})
The most common cause for that first error condition will be that the transaction had to be retried too frequently, meaning that too many users are trying to claim seats at the same time. A typical solution here would be to back off, i.e. have the client retry later.

Meteor setInterval and query

I want to have a function that checks every 5 seconds for all entries in database for some value is false and if finds then checks some logic condition and changes the value to true if the logic condition is met.
My function works well until I have something with isItReady: false in my collection. When I don't have, it obviously doesn't find anything and I start getting errors.
How should I do this correctly? I don't want to stop my interval because maybe something will be entered into the collection soon and then my inverval is stopped?
How can I do something like this:
If nothing matches my search criterea - productDate = Products.findOne({isItReady: false}); the interval is stopped and as soon as something new gets inserted I will start the inverval again?
var logicCheck = Meteor.setInterval( function () {
productDate = Products.findOne({isItReady: false}); //query to find all entries with isItReady: false
var timeNow = Date();
var timeCreated = productDate.startOfCountdown;
timeCreated = timeCreated.toString(); //converts timeCreated from object to String(in Mongo its a object)
var productId = productDate._id;
console.log(typeof timeNow) //string
console.log(typeof timeCreated) //string
console.log(timeNow + "timeNow")
console.log(timeCreated + "timeCreated")
if (timeCreated <= timeNow) {
console.log("check") //this works well
Products.update({_id: productId}, {$set: {isItReady: true}}, function(error, result) {
console.log(productId) //all good
if (error){
console.log(error.reason) //check the error
} else{
console.log("File with the id: " + result + " just get update")
}
});
}
}, 5000);
Your approach of polling MongoDB every 5 seconds is very non-Meteoric. You'd be much better off creating a Tracker.autorun function to instantly react to any product that has isItReady == false
For example:
Tracker.autorun(function(){
var notReadyProducts = Products.find({ isItReady: false });
notReadyProducts.forEach(function(p){
if ( your logic ... ) Products.update({ _id: p._id },{ $set: { isItReady: true }});
});
});
This assumes by the way that you're publishing (on the server) and subscribing to (on the client) a set of Products that is going to include these not ready products.
With this pattern no code will be running 99.99% of the time and then at the precise moment that a product is made not ready this code will kick in.
You might want to take a look at this video to learn more about reactive programming and how it completely changes the way you approach common problems. There are many other resources available as well.

Categories