Cannot get data from class method taken from Firebase - javascript

I have a difficulties to solve my problem. I am connected to Firebase, and trying to set a connection and check if the name is in the database.
class Db {
connect (path) {
const db = firebase.firestore();
const docRef = db.doc(path);
return docRef;
}
exist (name, path) {
this.connect(path).get()
.then(querySnapshot => {
console.log(querySnapshot.data().Users);
const users = querySnapshot.data().Users;
// return users;
if (users.indexOf(name) > -1) {
console.log('yes');
return true
} else {
console.log('no');
return false
}
})
.catch(e => {
console.log(e);
})
}
}
let databaseurl = '2048/database';
let database = new Db();
console.log(database.exist('kytek', databaseurl)); //undefined
from console log I am getting undefined
but console log return an array, I am not sure why...
part with return before if:
console.log(querySnapshot.data().Users);
const users = querySnapshot.data().Users;
return users;
and consol.log returns array
but return returning undefined
any ideas?

You're not getting your true/false values because the exist() function you've written is asynchronous, and you aren't returning the interior function. If you want to return data from the interior this.connect().get() function, you need to return that outer function... what I mean is:
exist (name, path) {
return this.connect(path).get()
.then(querySnapshot => {
// etc... rest of function here
If that doesn't work for you, you may also have to mark the function as async and use await to wait to receive the result:
async exist (name, path) {
return this.connect(path).get()
.then(querySnapshot => {
// etc... rest of function here
let databaseurl = '2048/database';
let database = new Db();
let result = await database.exist('kytek', databaseurl);
console.log(result);

Related

Javascript - Convert function that have a least two callbacks style to promise style

I moving from callback style to promise style to avoid the callback that made me hard to maintain the code. I use util module to import and convert the callback function with util.promisify(), but the question is if the function have at least two callbacks. How the function will return in promise form, then how can I manage the code to get the promise from two conditional callback.
Callback style example.
loginAccount = async ({email,password},success,unsuccess) => {
this.db.query(`SELECT id, password FROM account WHERE email = ?`,[email],(err,result)=>{
if (err) throw err
if (result.length === 0) {
unsuccess("ไม่มีบัญชีนี้อยู่ในระบบ ตรวจสอบอีเมลว่าถูกต้องหรือไม่")
return
}
const account = result[0]
bcrypt.compare(password,account.password,(error,response)=>{
if (err) throw err
if (!(response)) {
unsuccess("รหัสผ่านไม่ถูกต้อง")
return
}
this.getUser(account.id,success,unsuccess)
})
})
}
I can convert into promise form like this, but I don't know how to return success or unsuccess.
loginAccount = async ({email,password},success,unsuccess) => {
const result = await this.query(`SELECT id, password FROM account WHERE email = ?`,email)
if (result.length > 0) {
const account = result[0]
const response = await this.compareHash(password,account.password)
if (response) {
return this.getUser(account.id)
}
else {
// return unsuccess
// Wrong password
}
}
else {
// return unsuccess
// No account in the database
}
}
another example of callback style and promise style.
registerAccount = async ({firstname,lastname,email,password},success,unsuccess) => {
this.hasAccount(email,(exist)=>{
if (exist === true) {
unsuccess("บัญชีนี้เคยลงทะเบียนไว้แล้ว")
return
}
bcrypt.hash(password,this.saltRound,(err,hash)=>{
if (err) throw err
this.db.query(`INSERT INTO account (firstname,lastname,email,password) VALUES(?,?,?,?);
SET #user_id = LAST_INSERT_ID(); INSERT IGNORE INTO role (id) VALUES (#user_id);
INSERT INTO student_data (id) VALUES (#user_id);
INSERT INTO profile_image (id) VALUES (#user_id);`,
[firstname,lastname,email,hash],
(err,result)=>{
if (err) throw err
success()
})
})
})
}
registerAccount = async ({firstname,lastname,email,password},success,unsuccess) => {
const exist = await this.hasAccount(email)
if (exist) {
// unsuccess function return
// account is already exists.
return
}
const hash = await this.generateHash(password,this.saltRound)
const registerQuery = `INSERT INTO account (firstname,lastname,email,password) VALUES(?,?,?,?);
SET #user_id = LAST_INSERT_ID(); INSERT IGNORE INTO role (id) VALUES (#user_id);
INSERT INTO student_data (id) VALUES (#user_id);
INSERT INTO profile_image (id) VALUES (#user_id);`
const result = await this.query(registerQuery,[firstname,lastname,email,hash])
// success function
}
this.query, this.compareHash and this.generateHash, I'm using the library called util.promisify() to get promise function.
this.query come from mysql query function, this.compareHash and this.generateHash both are come from bcrypt.compare and brcypt.hash
this.compareHash will compare the hash by using plain password and hash password.
this.generataHash will generate the hash by using plain password and salt round to return a hash password.
this.getUser function.
getUser = async (id) => {
const result = await this.query(`SELECT * FROM profile_view WHERE id = ?`,id)
return result[0]
}

Calling async / await function and receiving a value back

I have got a function in an API library that calls firestore and gets data back. This part works fine:
export const getUserByReferrerId = async id => {
let doc = await firestore
.collection(FIRESTORE_COLLECTIONS.USERS)
.where('grsfId', '==', id)
.get()
.then(querySnapshot => {
if (!querySnapshot.empty) {
console.log ("we found a doc");
// use only the first document, but there could be more
const snapshot = querySnapshot.docs[0];
console.log ("snapshot", snapshot.id);
return snapshot.id // uid of the user
}
});
}
I am calling this library from a component. I have another function that runs on a button click. I cannot figure out how to get the value from the async api call.
I have tried this - a promise appears when I console.log the return:
testCreditFunction = (id) => {
let uid = getUserByReferrerId(id).then(doc => {
console.log("uid1", doc);
});
}
I have also tried this - log shows null for uid.
testCreditFunction = (id) => {
let uid = '';
(async () => {
uid = await getUserByReferrerId(id);
console.log ("uid in the function", uid);
})();
}
I have seen this question asked a few times and I have tried several of the answers and none are working for me. The odd thing is that I have done this same thing in other areas and I cannot figure out what the difference is.
Change your funtion to this.
export const getUserByReferrerId = async id => {
return await firestore
.collection(FIRESTORE_COLLECTIONS.USERS)
.where('grsfId', '==', id)
.get();
}
Try getting data this way.
testCreditFunction = (id) => {
let querySnapshot = '';
(async () => {
querySnapshot = await getUserByReferrerId(id);
const snapshot = querySnapshot.docs[0];
console.log ("snapshot", snapshot.id);
})();
One thing I notice right off the bat is that you're mixing async/await & .then/.error. While it's not strictly forbidden it definitely makes things more difficult to follow.
As others have mentioned in the comments, you need to make a return for the promise to resolve (complete). Here's how you might write getUserByReferrerId (using async/await).
const getUserByReferrerId = async id => {
const usersRef = firestore.collection('users');
const query = usersRef.where('grsfId', '==', id);
const snapshot = await query.get();
if (snapshot.empty) {
console.log('no doc found');
return;
}
console.log('doc found');
const doc = snapshot.docs[0]; // use first result only (there might be more though)
return doc.id
}
You'll notice how I split up the query building steps from the snapshot request. This was intentional as the only promised function in this is the get request.
Using this getUserByReferrerID can be done with a simple await. Just be sure to check that the result isn't undefined.
const userID = await getUserByReferrerId('someid')
if (!userID) {
console.log('user not found');
}
console.log(`User ID is ${userID}`);
p.s - I would recommend renaming the function to getUserIdByReferrerId to more accurately reflect the fact that you're returning an ID and not a user doc. Alternatively, you can return the user object and leave the name as is.

Need help in fetching document with reference id in firestore using reactjs

I'm a newbie with firestore and reactjs. I'm trying to write a simple react code to fetch documents from 2 collections
user-habits {email, habitid}
habits {habitid, content +few more fields}
each user can have one or more habits
I'm trying to execute the following react code to
export async function getHabit(habitid) {
let result = [];
return db.collection('habits')
.where('habitid','==',`${habitid}`)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
result.push({...doc.data()})
})
return result;
})
}
export async function getUserHabit(email) {
let result = [];
return db.collection('user-habits')
.where('email','==',`${email}`)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
let habitid = doc.data().habitid
let habit = getHabit(habitid)
result.push({...doc.data(), ...habit, uid:doc.id})
})
return result;
})
}
For some reason, I always get blank data on getHabit call. console log statement, shows that a Promise is returned by getHabit, which resolves after the code is completely executed. Thanks in advance
You can use async and await inside the getUserHabit() function to wait for the promise returned by getHabit() to be resolved
export async function getUserHabit(email) {
let result = [];
return db.collection('user-habits')
.where('email','==',`${email}`)
.get()
.then(snapshot => {
snapshot.forEach(async doc => {
let habitid = doc.data().habitid
let habit = await getHabit(habitid)
console.log(habit) // will log the habit value
result.push({...doc.data(), ...habit, uid:doc.id})
})
return result;
})
}
You don't need to return the db.collection in this case. Since you're calling from the front end, you can just reference the collection path and wait for Firestore to return what you're looking for. Also, you don't need to use the .then() approach with async/await. If you're expecting an array in return, try something like this:
export async function getHabit(habitid) {
let result = [];
let get = await db.collection('habits').where('habitid','==',`${habitid}`).get()
get.forEach(doc => {
result.push({...doc.data()})
})
return result;
})
}
export async function getUserHabit(email) {
let result = [];
let get = await db.collection('user-habits').where('email','==',`${email}`).get()
get.forEach(doc => {
let habitid = doc.data().habitid
let habit = getHabit(habitid)
result.push({...doc.data(), ...habit, uid:doc.id})
})
return result;
})
}

Param didn't pass to local function in firebase cloud functions

I have a firebase cloud function as follows:
exports.foo = functions.database
.ref("/candidates/{jobTrack}/{candidateId}")
.onCreate((snap, context) => {
const candidate = snap.val().candidate;
const jobTrack = context.params.jobTrack;
const jobsRef = admin.database().ref("jobs");
return jobsRef
.child(jobTrack)
.once("value")
.then(jobs => {
const promises = [];
jobs.forEach(job => {
promises.push(job.val());
});
return Promise.all(promises);
})
.then(jobs => {
return jobs.forEach(job => {
var percent = getMatchedPercent(candidate, job);
if (percent >= 0.9) {
admin
.database()
.ref("feeds")
.child(job.feedId)
.child("upcomingWeek")
.push(candidate); // add to team's feed
}
});
})
.catch(err => {
console.log("firebase got an error: ", err);
});
});
In function foo, I call a local non-cloud function getMatchedPercent which is defined as below:
const getMatchedPercent = (candidate, job) => {
console.log("In get percent: ", candidate, job);
// do something
};
The problem is when I checked job.val() in foo before calling getMatchedPercent, I can see valid data got printed from console for job.val(). When once get in getMatchedPercent, I tried to print job, it complains it's undefined.
Is there anything I missed? Why the information of job can be lost during calling a function? Thanks!
Your problem is caused by these lines:
const promises = [];
jobs.forEach(job => {
promises.push(job.val());
});
return Promise.all(promises);
job.val() returns an object (of the data) not a promise, so Promise.all() incorrectly interprets it as a resolved promise with no value. In your next block of code, the array jobs is an array of undefined values rather than the data you were expecting.
To fix this, you would instead return the array of values rather than using Promise.all().
const jobValues = [];
jobs.forEach(job => {
jobValues.push(job.val());
});
return jobValues;
But because no asyncronous work is taking place here you can flatten your Promise chain. By doing so, you will use less memory because you won't need an array containing of all of your job.val() objects at once.
exports.foo = functions.database
.ref("/candidates/{jobTrack}/{candidateId}")
.onCreate((snap, context) => {
const candidate = snap.val().candidate;
const jobTrack = context.params.jobTrack;
const jobsRef = admin.database().ref("jobs");
return jobsRef
.child(jobTrack)
.once("value")
.then(jobs => {
const promises = []; // will contain any team feed update promises
jobs.forEach(jobSnapshot => { // This is DataSnapshot#forEach
const job = jobSnapshot.val();
const percent = getMatchedPercent(candidate, job);
if (percent >= 0.9) {
promises.push(
admin
.database()
.ref("feeds")
.child(job.feedId)
.child("upcomingWeek")
.push(candidate) // add to team's feed
);
}
});
return Promise.all(promises);
})
.catch(err => {
console.log("Failed to update team feeds: ", err);
});
});
However, this still has another problem where some of the feed updates may succeed and others may fail which leaves your database in an unknown state. So instead you might want to consider writing to the database atomically (all data is written, or nothing at all).
This could be achieved using:
exports.foo = functions.database
.ref("/candidates/{jobTrack}/{candidateId}")
.onCreate((snap, context) => {
const candidate = snap.val().candidate;
const jobTrack = context.params.jobTrack;
const jobsRef = admin.database().ref("jobs");
return jobsRef
.child(jobTrack)
.once("value")
.then(jobs => {
const pendingUpdates = {}; // "path: value" pairs to be applied to the database
const feedsRef = admin.database().ref("feeds");
jobs.forEach(jobSnapshot => { // This is DataSnapshot#forEach
const job = jobSnapshot.val();
const percent = getMatchedPercent(candidate, job);
if (percent >= 0.9) {
const pushId = feedsRef.push().key; // push() without arguments doesn't write anything to the database, it just generates a new reference with a push ID we can use.
const path = job.feedId + "/upcomingWeek/" + pushId;
pendingUpdates[path] = candidate; // queue add to team's feed
}
});
// apply all updates in pendingUpdates object,
// relative to feedsRef as an all-or-nothing operation.
// e.g. pendingUpdates["feed001/upcomingWeek/9jksdfghsdjhn"] = "someUserId"
// will be written to "feeds/feed001/upcomingWeek/9jksdfghsdjhn"
return feedsRef.update(pendingUpdates); // commit changes
})
.catch(err => {
console.log("Failed to apply all feed updates: ", err);
});
});

Problems with optional promise that has nested promises

I have a server GET request where I am trying to create a promise from a function but it's not going too well. I need this promise to be optional, if that's possible, so that if the result is empty it should be ignored (I've marked where it shouldn't carry on with the function with the words IGNORE FUNCTION).
The idea is that it loops through the docs inside the snapshot, and adds them to an array calls jamDocs. Then if that array isn't empty it should loop through each jam and call an async function for each of them. This then sets the jam's hostName. When this is completed, the original promise should return the jamDocs.
If the jamDocs are empty, the whole promise should be ignored. Finally, after other promises are completed (there is one called profilePromise that returns a profile object), the jams should be assigned to the profile object, and the object sent back to the client with a 200 status code.
My code:
var jamsQuery = firebase.db.collection('users')
.where("hostId", "==", id)
.orderBy("createdAt")
.limit(3)
var jamsPromise = jamsQuery.get().then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
var jam = doc.data()
jam.id = doc.id
jamDocs.push(jam)
})
if (!snapshot.length) { // Error: Each then() should return a value or throw
return // IGNORE FUNCTION
} else {
var jamNamePromises = jamDocs.map(function(jam) {
var jamRef = firebase.db.collection('users').doc(jam.hostId)
return jamRef.get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
})
})
Promise.all(jamNamePromises)
.then(function() {
return jamDocs
})
.catch(...)
}
})
// Later on
Promise.all(profilePromise, jamsPromise)
.then(objectArray => {
var profile = objectArray[0]
myProfile.jams = jamDocs
return res.status(200).send(myProfile);
})
.catch(
res.status(500).send("Could not retrieve profile.")
)
I get errors like: Each then() should return a value or throw and Avoid nesting promises. How do I fix this? Thanks!
Your then on this line doesn't return anything and also has nested promises in it.
var jamsPromise = jamsQuery.get().then(snapshot => {
You should refactor it, to move the nested promises outside of this then:
var jamsPromise = jamsQuery.get()
.then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
var jam = doc.data()
jam.id = doc.id
jamDocs.push(jam)
})
if (!snapshot.length) {
return // IGNORE FUNCTION
} else {
var jamNamePromises = getJamDocsPromises();
return Promise.all(jamNamePromises);
})
.catch(...)
});
function getJamDocsPromises(jamDocs) {
return jamDocs.map(function(jam) {
var jamRef = firebase.db.collection('users').doc(jam.hostId)
return jamRef.get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
});
}
If the result is empty it should be ignored (shouldn't carry on with the function). If that array isn't empty it should loop through each jam
Don't make it more complicated than it needs to be. Looping over an empty collection does nothing anyway, so you don't need a special case for that. Just continue normally with the empty array.
var jamsPromise = jamsQuery.get().then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
jamDocs.push(Object.assign(doc.data(), {id: doc.id}))
})
return Promise.all(jamDocs.map(function(jam) {
return firebase.db.collection('users').doc(jam.hostId).get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
})
}))
})
I get errors like: Each then() should return a value or throw
You had forgotten a return in front of the Promise.all.
and Avoid nesting promises. How do I fix this?
There's nothing wrong with nesting promises, but in your case the .then(() => jamDocs) was not necessary since the Promise.all already resolves with an array of all the jams that the inner promises fulfill with.

Categories

Resources