I have the following in the my code. I'm trying to prevent a user from reviewing someone twice so therefore it should stop executing code in the IF statement and end firebase calls.
However, it continues executing the rest of the ".then" statements. I tried changing it to end() but that didn't work either. What am I doing wrong?
exports.reviewUser = (req, res) => {
db.collection("users")
.doc("user")
.get()
.then((doc) => {
if (doc.exists) {
return res
.status(400)
.json({ error: "You cannot review someone twice." })
.send();
}
})
.then(() => {
//Additional functions
})
.then(() => {
//Additional functions
})
}
Update:
I followed the answer and here's my edited code that works for future reference.
exports.reviewUser = async(req, res) => {
let existingReview = await checkReviewExists(userid);
if (existingReview) {
return res
.status(400)
.json({ error: "You cannot review someone twice." })
.end();
}
db.collection("users")
.doc("user")
.then(() => {
//Additional functions
})
.then(() => {
//Additional functions
})
}
function checkReviewExists(id1) {
return db
db.collection("users")
.doc(id1)
.get()
.then((doc) => {
if (doc.exists) {
console.log("cannot review twice");
return true;
}
else
return false;
});
}
As per the Firebase doc, we could end HTTP function with send(), redirect(), or end(). However, in your case you are resolving the firestore Promise with multiple .then(). So here, though the first one returns, it goes to the second then()
It's called Promises chaining
The above reference gives detailed info with examples. I prefer to go with one .then() or simply async / await would do the work.
Related
I have a function that is supposed to return a user's email from using the Firebase admin API getUser(uid) function,
function getEmail(uid){
var email;
admin.auth().getUser(uid)
.then((userRecord) => {
return userRecord.email;
})
.catch((error) => {
console.log('Error fetching user data:', error);
});
}
But in my other function when I make a variable that calls the function,
email = getEmail(uid);
the value in email is undefined, because the getUser function has returned a promise. How do I make the function getEmail wait to get the value of userRecord before returning?
I've tried adding await statements in different parts of the function but I'm not sure how to do it correctly. I'm a beginner in using the Google API.
Return the Promise that you get from admin.auth().getUser(uid) in your getEmail function.
function getEmail(uid) {
return admin.auth().getUser(uid)
.then((userRecord) => {
return userRecord.email;
})
.catch((error) => {
console.log('Error fetching user data:', error);
});
}
Then either use the returned Promise to chain a then callback.
getEmail(uid).then(email => {
console.log(email);
});
Or await it in an async function.
async function main() {
const email = await getEmail(uid);
console.log(email);
}
main();
Edit
As you explained in your comment below:
basically, I just want the code to run as if it were all synchronous/ line-by-line.
It's impossible to make asynchronous code synchronous, but it is possible to make it run line-by-line with async / await syntax. Promises that are being awaited in an async function will run line by line as you're telling the code to wait for it to resolve before going to the next line.
I've modified the code from your CodePen to include the aforementioned syntax. We're still using the same getEmail function that returns the promise from admin.auth().getUser(uid) and just wait for it to finish before doing something with the result.
function getEmail(uid) {
return admin.auth().getUser(uid)
.then((userRecord) => {
return userRecord.email;
})
.catch((error) => {
console.log('Error fetching user data:', error);
});
}
app.post("/sendx", async (req, res) => {
const uid = req.body.x.user.uid;
const email = await getEmail(uid);
let verified = false;
/*console.log(email.substring(email.indexOf('#')));
if (email.substring(email.indexOf('#')) == "#xxx.edu") {
verified = true;
}*/
res.send({ verified });
});
I hope this clears it up.
I am trying to add a new user with firebase-admin and then to save a new document in a custom collection.
Sample code following:
admin.auth().createUser(user)
.then((record) => {
user.uid = record.uid;
userCollection.doc(record.uid).set({...user})
.then(writeResult => {
resolve();
})
.catch(reason => {
reject(reason)
});
})
.catch((err) => {
reject(err);
});
The problem is, if the userCollection.doc(record.uid).set({...user}) fails, I expect the nested catch (with reason as param) to be called. Instead, always the outer one is called (with err as param).
Is there something wrong with the SDK or am I doing something wrong?
Thank you
This is because you don't return the promise returned by userCollection.doc(record.uid).set() and therefore you don't return the promises returned by the subsequent then() and catch() methods. In other words you don't return the promises chain.
But, actually, you should chain your Promises as follows and avoid a then()/catch() pyramid.
admin
.auth().createUser(user)
.then((record) => {
user.uid = record.uid;
return userCollection
.doc(record.uid)
.set({ ...user })
})
.catch((err) => {
// Here you catch the potential errors of
// the createUser() AND set() methods
console.log(JSON.stringify(err));
});
More details here, here and here.
I have a problem with a callable firebase cloud function (exports.listAllUsers).
Backend: Nodejs & Firebase-Cloud_functions to use admin.auth().listUsers
Problem: Result (usersList; an array with the uid of the users) is OK in cloud-functions-emulator (log) but NOT in the client (console.log(usersList) in browser is null)
Maybe a problem related with...: bad understanding of promises. A second code example is working with async/await but not working with .then().
The code of the function listAllUsers is basically copy&paste from docs (original code snipet: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users). My code modifications are 5 (comments in the code), to get a list of users uid:
exports.listAllUsers = functions.https.onCall(() => { // -1) callable function
const usersList = ['12323211'] // ----------------------2) first user uid, just a sample
function listAllUsers (nextPageToken) {
// List batch of users, 1000 at a time.
admin.auth().listUsers(1000, nextPageToken)
.then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
usersList.push(userRecord.uid) // --------------3) get users uids
})
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken)
}
console.log(usersList) //-------------------------4) list users uid (cloud functions console)
return usersList //-------------------------------5) return to the client the same as showed at the console
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
}
// Start listing users from the beginning, 1000 at a time.
listAllUsers()
})
The method in the client is...
getUsersList: async function (userType) {
const usersList = await this.$fb.functions().httpsCallable('listAllUsers')()
console.log('usersList: ', usersList)
}
I am using firebase emulators. Cloud functions log is OK, you can see the sample uid and the other uids:
cloud function emulator console output
But I don't get the same in the client:
client console output
I think I am doing something wrong related with promises... because a simplification of the code is working with async/await:
exports.listAllUsers = functions.https.onCall(async () => {
try {
listUsersResult = await admin.auth().listUsers()
return listUsersResult
} catch (error) {
console.log('Error listing users:', error)
return null
}
})
Browser console output (reduced code with async/await)
But the same is not working with then()...
exports.listAllUsers = functions.https.onCall(() => {
admin.auth().listUsers()
.then((listUsersResult) => {
return listUsersResult
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
})
Browser console output (reduced code with .then())
I can refactor the initial snipet of code with async/await, but I am interested in the solution with the original code (.then() flavor; I always use async/await because I am quite new at js)... Anyone can help me? Thanks!
This is because with the async/await version you correctly return listUsersResult by doing
listUsersResult = await admin.auth().listUsers()
return listUsersResult
but, with the then version, you don't. You should return the entire promises chain, as follows:
exports.listAllUsers = functions.https.onCall(() => {
return admin.auth().listUsers() // !!! Note the return here !!!
.then((listUsersResult) => {
return listUsersResult
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
})
Finally I decided to code an async/await version for my cloud function. The then version in the first code snipet requires more than just adding the return to the entire promises chain (it initially complains because of the recursivity, maybe, asking me to add asyncto the wrapper function listAllUsers... I'd like the then version to just copy&paste from the firebase docs, but it wanted more).
I'd like to share this homemade (but initially tested) version as a sample with async/await without recursivity to list users with admin.auth().listUsers(maxResults?, pageToken?):
// get list of users
exports.listAllUsers = functions.https.onCall(async () => {
const usersList = []
try {
let listUsersResult
let nextPageToken
do {
if (listUsersResult) {
nextPageToken = listUsersResult.pageToken
}
// eslint-disable-next-line no-await-in-loop
listUsersResult = await admin.auth().listUsers(1000, nextPageToken)
listUsersResult.users.forEach((userRecord) => {
usersList.push(userRecord.uid)
})
} while (listUsersResult.pageToken)
return usersList
} catch (error) {
console.log('Error listing users:', error)
return null
}
})
I'm new to React Native, Javascript and Firestore. The issue is that I'm trying to read a firestore database and wait for the response to display information on the next page before it is rendered.
Here is the controller code:
_signIn = async () => {
try {
// Google sign in and connect to Firebase
... This code is working as expected
// Connect to the Firebase Firestore and store the connection
CesarFirestore.setFirestore(firebase.firestore());
let cFS = CesarFirestore.getInstance();
cFS.saveLoginDetails(this.cUser);
// THIS IS THE CALL TO GET THE STATUS OF THE USER
await cFS.readAvailability(this.cUser);
this.setState({loggedIn: true});
} else {
... INVALID login coding works fine
}
} catch (error) {
... CATCH coding works fine
}
}
};
From the above marked line, the following code is executed:
async readAvailability(cUser) {
let tuesdayDate = new CesarDate().getTuesday();
let availableRef = CesarFirestore.getFirestore().collection('available');
let availableQuery = await availableRef
.where('tuesdayDate', '==', tuesdayDate)
.where('userId', '==', cUser.getUserId())
.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
cUser.setTuesdayAvailability('NOT_FOUND');
}
snapshot.forEach(doc => {
cUser.setTuesdayAvailability(doc.get('status'));
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
So the readAvailability(cUser) code should wait for the availableQuery to return results. The results would be then stored in the cUser class which is available to the rest of the application.
What happens is sometimes the result is available and sometimes the result is null. I thinking this is because doc is undefined which I have confirmed via the debugger.
Any help would be great and thank you in advance
If you await for a function, it must return a value, or a promise that resolves to a value later on. Since you're instead consuming the promise (in your then() handler), there is no way for the interpreter to know what to wait on.
async readAvailability(cUser) {
let tuesdayDate = new CesarDate().getTuesday();
let availableRef = CesarFirestore.getFirestore().collection('available');
let snapshot = await availableRef
.where('tuesdayDate', '==', tuesdayDate)
.where('userId', '==', cUser.getUserId())
.get()
if (snapshot.empty) {
console.log('No matching documents.');
cUser.setTuesdayAvailability('NOT_FOUND');
}
snapshot.forEach(doc => {
cUser.setTuesdayAvailability(doc.get('status'));
});
return true;
}
I'd usually actually make readAvailability return the user it modified, but the above should work too.
I thought I had a simple function:
database change trigger (.onUpdate)
find out which change is possibly important for a notification (prepareNotifications(change))
ask firebase if there are records that want a notification about that change (getDatabaseNotifications(changeWithNotification))
sent notifications (sentNotifications(changeWithNotification))
I'm stuck for a couple off days now on how to resolve the Firebase call before moving on.
tried to Promise.all([getDatabaseNotifications()])
tried to chain this function like this:
changes
then firebase call
then sent notifiactions
What is happening:
I get the changes,
The call to Firebase is done,
But before waiting for the result it moves on to sending notifications.
It finds no notifications in Firebase (but there are notifications!)
It's gathering the notifications (array [])
... here I push a test notification ...
It's sending 1 notification (the test notification)
Then it resolves the Firebase notifications (this should be resolved before).
Then the function stops without doing anything anymore.
This is how my function looks now. Can someone explain how I can wait on Firebase?
exports.showUpdate = functions.firestore
.document('shows/{showId}')
.onUpdate((change, context) => {
return prepareNotifications(change) // check if and which notifications to get out of the firebase database
.then(changes => {
console.log('changes');
console.log(changes);
if(changes) {
const gatherData = [];
changes.forEach(change => {
console.log('change');
console.log(change);
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
.catch(err => {
console.log(err);
})
})
return gatherData;
}
return null;
})
.then(notifications => {
console.log('notifications');
console.log(notifications);
notifications.push(testData); // add some test notifications
if (notifications && notifications.length > 0) {
sentNotifications(notifications); // sent notifications
return 'sending notifications';
}
return 'no notifications to sent';
})
.catch(err => {
Sentry.captureException(new Error(`Showupdate sending notifications not ok. Error message: ${err.message}`));
})
});
Updated code which works! thanks to your examples.
exports.showUpdate = functions.firestore
.document('shows/{showId}')
.onUpdate((change, context) => {
return prepareNotifications(change) // check if and which notifications to get out of the firebase database
.then(changes => {
if(changes) {
return getDbRecords(changes);
}
})
.then(notifications => {
if (notifications && notifications.length > 0) {
sentNotifications(notifications); // sent notifications
return 'sending notifications';
}
return 'no notifications to sent';
})
.catch(err => {
Sentry.captureException(new Error(`Showupdate sending notifications not ok. Error message: ${err.message}`));
})
});
function getDbRecords(changes) {
const gatherData = [];
const gatherDataPromises = [];
changes.forEach(change => {
gatherDataPromises.push(
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push(...data);
})
);
});
return Promise.all(gatherDataPromises)
.then(() => { return gatherData }
);
}
This section of your code doesn't handle promises properly, it creates a bunch of work but then will return gatherData before any of it has happened, which is why you don't see any notifications:
if(changes) {
const gatherData = [];
changes.forEach(change => {
console.log('change');
console.log(change);
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
.catch(err => {
console.log(err);
})
})
return gatherData;
}
Notably, you probably want that return gatherData to be chained off the set of promises that are generated by the entire set of calls to getDatabaseNotifications.
Something like:
if(changes) {
const gatherData = [];
const gatherDataPromises = [];
changes.forEach(change => {
console.log('change');
console.log(change);
gatherDataPromises.push(
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
);
});
return Promise.all(gatherDataPromises)
.then(() => { return gatherData });
}
I removed the catch statement to allow the error to bubble up to the top level catch.
Caution: I have not tested this, as I don't have sample data or the code for getDatabaseNotifications, but the general approach should solve your current problem. Likewise, it allows all the calls to getDatabaseNotifications to run in parallel, which should be significantly faster than just awaiting on them in sequence.
That said, you do have other problems in this code -- for example, the return null just below the block I am discussing will likely lead you into trouble when you try to use notifications.push() in the following then() (but this also appears to be test code).
I think it's because of the async nature of the methods. So, instead of waiting "getDatabaseNotifications()" to finish it's job, it jumps into ".then(notifications =>{}" and in this case gatherData returns empty.
putting await before calling the method might work.
await getDatabaseNotifications(change.showId, change.ring, change.ringNumber)