Angular Firebase Get Data Sync - javascript

I want to get balance data from firebase. But it must be real-time.
I write the code below. The console output is "Balance: 0". After this output, it gets firebase data and writes it. So, before data come from firebase, the code continues and get balance function returns 0. I can't compare balance because of that. Actual data comes after that.
checkBalance() {
let balance = this.authService.getBalance()
result = this.authService.getBalance() >= this.price ? true : false
console.log("Balance: " + balance);
return result;
}
AuthService:
getBalance() {
let docRef = this.angularFirestore.collection(`users`).doc(JSON.parse(localStorage.getItem('user')).uid);
docRef.get().toPromise().then(function (doc) {
if (doc.exists) {
console.log(doc.data());
console.log(doc.data().balance);
return parseFloat(doc.data().balance);
} else {
// doc.data() will be undefined in this case
console.log("Doc can not find!");
return 0.0;
}
}).catch(function (error) {
console.log("Error getting document:", error);
return 0.0;
});
return 0.0;
}

Original Answer
Steps
Mark getBalance method async, and make it return your Promise
Mark checkBalance method async
Change the first line as follows: let balance = await this.authService.getBalance()
Sample
async getBalance() {
const docRef = this.angularFirestore.collection(`users`).doc(JSON.parse(localStorage.getItem('user')).uid);
return docRef.get().toPromise().then(function (doc) {
if (doc.exists) {
console.log(doc.data());
console.log(doc.data().balance);
return parseFloat(doc.data().balance);
} else {
// doc.data() will be undefined in this case
console.log("Doc can not find!");
return 0.0;
}
}).catch(function (error) {
console.log("Error getting document:", error);
return 0.0;
});
}
async checkBalance() {
const balance = await this.authService.getBalance();
const result = balance >= this.price ? true : false;
console.log("Balance: " + balance);
return result;
}
JS Reference
https://javascript.info/async-await
Update For Angular 12 or Greater
Deprecation of toPromise in rxjs 7
Now instead of toPromise you have to use lastValueFrom or firstValueFrom from rxjs
import { firstValueFrom} from "rxjs";
async getBalance() {
const docRef = this.angularFirestore.collection(`users`).doc(JSON.parse(localStorage.getItem('user')).uid);
return firstValueFrom(docRef.get()).then(function (doc) {
if (doc.exists) {
console.log(doc.data());
console.log(doc.data().balance);
return parseFloat(doc.data().balance);
} else {
// doc.data() will be undefined in this case
console.log("Doc can not find!");
return 0.0;
}
}).catch(function (error) {
console.log("Error getting document:", error);
return 0.0;
});
}
Rxjs Reference
https://rxjs.dev/deprecations/to-promise

Related

How to export/read data from async function that's querying firebase

Learning how to use react by creating a project.
I am trying to filter the data by when it was added into the collection. Then I am trying to add all the totalCalories in a certain amount of time so i can display it in form using react.
However, I do not know how to get the values for totalCalories and also get the other fields like mealTime. I want to display all of them in my app.js However when i do i get undefined, or says promise pending.
Any help would be massively appreciated. Thanks
firebase.js
export async function run() {
const time = 48;
const timediff = moment(new Date()).subtract(time, "hours")._d;
await db
.collection("/food/meals")
.where("mealTime", ">=", timediff)
.get()
.then((qSnapshot) => {
let totalCalories = 0;
qSnapshot.forEach(function (doc) {
totalCalories += doc.data().calories;
console.log(doc.id, " -> ", doc.data());
});
console.log(totalCalories);
return totalCalories;
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
}
run(); //returns nothing
console.log(run()); //Promise {<pending>}
console.log(run().totalCalories); //undefined
Try this :
Instead of :
await db...
=>
return db...
run doesn't need to be an async function anymore
run().then(totalCalories => console.log('totalCalories :', totalCalories));
Now run return a promise, when this promise is resolved it return the totalCalories returned by your db query
Full code :
function run() {
const time = 48;
const timediff = moment(new Date()).subtract(time, "hours")._d;
return db // this is returned by the function (as a promise)
.collection("/food/meals")
.where("mealTime", ">=", timediff)
.get()
.then((qSnapshot) => {
let totalCalories = 0;
qSnapshot.forEach(function (doc) {
totalCalories += doc.data().calories;
console.log(doc.id, " -> ", doc.data());
});
console.log(totalCalories);
return totalCalories; //this is returned by db query
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
}
// if your code is an ES6 module
let result = await run();
console.log('Result :', result);
// else
run().then(result => console.log('Total : ', result));
// to export this as ES6 Module
export { run }
// or
export default run;
To return all the data + calculated value totalCalories you can do :
return { ...doc.data(), 'totalCalories': totalCalories };

Function return null, await result not applied

I am using the below function within post method. The async-await is used but in transferAmount totalBalance is not updated when I call the function inside the post route. The return from the function is not proper. I need guidance so that it returns the object with updated values.
async function transferAmount(fromAccountId, toAccountId, amount) {
const session = await mongoose.startSession();
const options= {session, new:true}
let sourceAccount, destinationAccount;
const BASICSAVNGS_MAX_BALANCE = 1500;
const result = {
newSrcBalance: 0,
totalDestBalance:0,
transferedAt:moment.now()
}
try {
session.startTransaction();
const source= await Account.findByIdAndUpdate(
{_id:sourceAccount._id},
{$inc:{balance:-amount}},
options
);
if(source.balance <0) {
// Source account should have the required amount for the transaction to succeed
const errorMessage='Insufficient Balance with Sender:';
throw new ErrorHandler(404,errorMessage);
}
const destination = await Account.findByIdAndUpdate(
{_id:destinationAccount._id},
{$inc:{balance:amount}},
options
);
// The balance in ‘BasicSavings’ account type should never exceed Rs. 50,000
if((destination.accountType.name === 'BasicSavings') && (destination.balance > BASICSAVNGS_MAX_BALANCE)) {
const errorMessage=`Recepient's maximum account limit reached`;
throw new ErrorHandler(404,errorMessage);
}
await session.commitTransaction();
result.transferedAt= moment.now() //*UPDATE THE TRANSFER TIMESTAMP
result.newSrcBalance = source.balance; //*UPDATE THE SOURCE BALANCE
session.endSession();
// finding total balance in destination account
await User.findById(destination.user.id, async function(err,user) {
if(err) {
const errorMessage=`Recepient not found!`;
console.log(err);
throw new ErrorHandler(404,errorMessage);
} else {
if(user.accounts) {
await Account.find({
'_id' :{$in:user.accounts}
}, function(err,userAccounts) {
totalDestBalance = userAccounts.reduce( (accumulator,obj) => accumulator+obj.balance,0);
result.totalDestBalance = totalDestBalance; //*UPDATE THE TOTAL BALANCE
console.log(result);
return result;
});
}
}
});
}
catch (error) {
// Abort transaction and undo any changes
await session.abortTransaction();
session.endSession();
throw new ErrorHandler(404,error);
} finally {
if(session) {
session.endSession();
}
}
}
module.exports = transferAmount;
Result of above function is
{
newSrcBalance: 940,
totalDestBalance: 1060,
transferedAt: 1594982541900
}
But inside the post request below it is {}
const result = await transferAmount(fromAccountId, toAccountId, amount);
You are not returning something inside the function.
User.findById - this receives a callback for returning something.
You can convert it as async/await syntax or have to resolve the result with promise.
Like below:
try {
const user = await User.findById(destination.user.id);
if (user.accounts) {
const userAccounts = await Account.find({ _id: { $in: user.accounts } });
totalDestBalance = userAccounts.reduce((accumulator, obj) => accumulator + obj.balance, 0);
result.totalDestBalance = totalDestBalance; //*UPDATE THE TOTAL BALANCE
console.log(result);
return result;
}
} catch (err) {
const errorMessage = `Recepient not found!`;
console.log(err);
throw new ErrorHandler(404, errorMessage);
}
Or:
return new Promise((resolve, reject) => {
User.findById(destination.user.id, async function(err, user) {
if (err) {
const errorMessage = `Recepient not found!`;
console.log(err);
reject(err);
} else {
if (user.accounts) {
await Account.find(
{
_id: { $in: user.accounts },
},
function(err, userAccounts) {
totalDestBalance = userAccounts.reduce((accumulator, obj) => accumulator + obj.balance, 0);
result.totalDestBalance = totalDestBalance; //*UPDATE THE TOTAL BALANCE
console.log(result);
resolve(result);
}
);
}
}
});
});
I may be wrong but i cannot see a return statement in you transferAmount function.

how to rewrite with callback for promised result

There is a function to generate a string and return it if it is in the User table.
function generateFortToken(len) {
let rs;
rs = randomstring.generate(len);
User.findOne({where: {fort_token: rs}})
.then(result => {
console.log("hit is : ", result);
if (!result) //need to return rs. but HOW?
})
.catch(err => {
console.log("Error search for fort token : ", err.message);
});
}
This generateFortToken is in module helper and is called from parent function like this:
user.fort_token = helper.generateFortToken(20);
This code does not work as many online posts pointed out since findOne returns a promise. But I am having hard time to rewrite it with callback to return the value of token generated.
The code you came up with is fine, but it can be improved. In particular, you've fallen into to the Promise constructor antipattern. In short, you're constructing a new promise (await new Promise(next => User.findOne(...))) when you can use the promise returned by User.findOne directly:
async function generateFortToken(len) {
for (let rs;; rs = randomstring.generate(len)) {
try {
if (await User.findOne({ where: { fort_token: rs }})) {
return rs;
}
}
catch (err) {
console.log('Error search for fort token : ', err.message);
}
}
}
Solved the problem with the code below:
generateFortToken : async function(len) {
let rs, bs, lp = true;
while (lp) {
rs = randomstring.generate(len);
await new Promise(next => {
User.findOne({where : {fort_token : rs}})
.then(result => {
if(!result) lp = false;
next();
})
.catch(err => {
console.log("Error search for fort token : ", err.message);
//next();
});
});
}
return rs;
},
In parent function:
user.fort_token = await helper.generateFortToken(20);
Inspired by Fernando Carvajal reply to the post.

How to get a Firestore transaction to return a promise?

I asked a question two days ago with a reply "Your method must return a Promise (or an Observable)."
I have altered my code to be exactly the same as the example at "https://firebase.google.com/docs/firestore/manage-data/transactions" but the problem is it passes the result as a console log but I need to wait for the write to complete at I need the result.
orderIndex() {
let db = this.firebase.firestore();
var sfDocRef = db.collection("cities").doc("SF");
db.runTransaction(function (transaction) {
return transaction.get(sfDocRef).then(function (sfDoc) {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
var newPopulation = sfDoc.data().population + 1;
if (newPopulation <= 1000000) {
transaction.update(sfDocRef, { population: newPopulation });
return newPopulation;
} else {
return Promise.reject("Sorry! Population is too big.");
}
});
}).then(function (newPopulation) {
console.log("Population increased to ", newPopulation);
}).catch(function (err) {
// This will be an "population is too big" error.
console.error(err);
});
}
I have spent a further two days trying to get a promise returned.
I have seen so many questions asking for help and receiving code suggestions in reply. Please help because I am new to this and have spent over four days on this problem.
By the way the code from firebase.google has an error in
return Promise.reject("Sorry! Population is too big.");
Error: "[ts] Property 'reject' does not exist on type '(resolver: (resolve: (val: IWhenable) => void, reject: (reason: any) => void, notify: (prog...'."
My previous question was at "How do I alter the promises in my function to stop it returning before the data arrives?"
Your function is not returning the promise and also in the then case you are not returning any value.
Try this:
orderIndex() {
let db = this.firebase.firestore();
var sfDocRef = db.collection("cities").doc("SF");
return db.runTransaction(function (transaction) { //Return here
return transaction.get(sfDocRef).then(function (sfDoc) {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
var newPopulation = sfDoc.data().population + 1;
if (newPopulation <= 1000000) {
transaction.update(sfDocRef, { population: newPopulation });
return newPopulation;
} else {
return Promise.reject("Sorry! Population is too big.");
}
});
}).then(function (newPopulation) {
console.log("Population increased to ", newPopulation);
return newPopulation; //Return the value
}).catch(function (err) {
// This will be an "population is too big" error.
console.error(err);
});
}
The following code updates the index, stores it back in firestore and returns the new number.
createOrderNo() {
const getDbIndex = new Promise(
(resolve, reject) => {
if (!this.orderLive) {
this.orderLive = true;
const sfDocRef = this.db.collection('eOrderIndex').doc('orderIndex');
sfDocRef.get().
then(function (sfDoc) {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
console.log('sfDoc.data()', sfDoc.data()['index'])
let index = sfDoc.data()['index'] + 1;
sfDocRef.update({ index: index });
resolve(index);
})
} else {
const reason = new Error('Already live');
reject(reason);
}
})
async function show(index) {
return new Promise(
(resolve, reject) => {
var message = 'New index ' + index;
resolve(message);
}
);
};
// call the promise
async function runPromise() {
try {
console.log('Before get');
let index = await getDbIndex;
let message = await show(index);
console.log(message);
console.log('After get');
}
catch (error) {
console.log(error.message);
}
}
(async () => {
await runPromise();
})();
}
Many thanks to Jecelyn Yeen at scotch.io

Javascript Promise Anti-Pattern error

I am using Ionic 2 with SQLite. I am getting an error, and I suspect it is due to me not using Promises correctly.
I get the following error:
ERROR REFRESHING CHATS: {}
TypeError {stack: (...), message: "Cannot read property 'executeSql'
of undefined"}
ts
private database = new SQLite();
public openDatabase(): Promise<Array<Message>> {
let promise: Promise<Array<Message>> = new Promise<Array<Message>>(resolve => {
if (this.database) {
let promiseChats: Promise<any> = this.refreshChats();
let promiseMessages: Promise<any> = this.refreshMessages();
Promise.all([promiseChats, promiseMessages]).then(() => { resolve(this.messages) });
} else {
this.database.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
let promiseChats: Promise<any> = this.refreshChats();
let promiseMessages: Promise<any> = this.refreshMessages();
Promise.all([promiseChats, promiseMessages]).then(() => { resolve(this.messages) });
}, (error) => {
console.log("OPEN ERROR: ", error);
});
}
});
return promise;
}
public refreshChats(): Promise<any> {
return this.database.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
this.populateChat(chatData, i);
}
}
return this.chats;
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
When I debug the code, and break on this.database.executeSql, this.database is not undefined. Also, you can see there is a check on this.database to see if it's undefined before too. Yet, the debugger steps out the Promise into the error, reporting the error shown.
Also, after the Promise finishes, the this.chats are populated. So I am very confused.
If anyone can suggest what I am doing incorrectly, I would appreciate it.
UPDATE
I have updated the code as follows, but still get the same error:
public openDatabase(): Promise<Array<Message>> {
let promise: Promise<Array<Message>> = new Promise<Array<Message>>(resolve => {
if (this.database) {
Promise.all([this.refreshChats(), this.refreshMessages()]).then(() => { resolve(this.messages) });
} else {
this.database.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
Promise.all([this.refreshChats(), this.refreshMessages()]).then(() => { resolve(this.messages) });
}, (error) => {
console.log("OPEN ERROR: ", error);
});
}
});
return promise;
}
public refreshChats(): Promise<any> {
return this.database.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
let promises: Array<any> = [];
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
promises.push(this.populateChat(chatData.rows.item(i)));
}
}
Promise.all(promises).then(() => {
return this.chats;
});
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
You are employing a bit of a Promise antipattern which I will describe to you. Although I can't see a reason it would cause your problem, it may help with debugging.
It's known as the explicit-construction anti-pattern and essentially it is when you create a new Promise unnecessarily.
For instance in your code, both of your functions create a new Promise at the start, where they could instead just return the promise used later;
public refreshChats(): Promise<Array<Chat>> {
if (!this.database || this.database === null) {
console.log('ERROR refreshing chats: database = ' + this.database);
}
return this.database.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
this.populateChat(chatData, i);
}
}
return this.chats;
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
This may help to break up your error so that you can see where it originates. If you produce any further information, ping me and I will update my answer.

Categories

Resources