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
}
})
Related
I am trying to test my react project locally with my computer and with my phone. I am using JavaScript not TypeScript.
When I run the project on my computer everything works fine, but when I try to load it on my phone, I get an error: Unhandled Rejection (TypeError): undefined is not an object (evaluating 'scheduleArr.forEach'). I thought I was using async and await correctly because this code workes on my computer. I'm confused as to why this code works on one platform but not the other.
async function getSchedule() {
let scheduleArr = await axios.get('api/schedule/')
.then(response => {
return response.data;
})
.catch((error) => {
console.log(`ERROR: ${error}`);
});
scheduleArr.forEach(game => {
/* do stuff */
}
});
I think this problem is directly related to async and await because when I comment out this function, my project loads correctly on my phone.
Can anyone help me understand what I'm doing wrong?
You can't use the async/await pattern with then. Either use it like :
async function getSchedule() {
try {
let scheduleArr = await axios.get("api/schedule/");
console.log(scheduleArr.data);
} catch (err) {
console.log(`ERROR: ${err}`);
}
scheduleArr.forEach(game => {
/* do stuff */
});
}
Or with the default pattern :
function getSchedule() {
axios
.get("api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
Note that I moved your foreach loop into the then. It is asynchronous and therefore need to be triggered only when you get the result of your api call.
I figured out what the issue was. It had nothing to do with async await or axios.
This was my code before
function getSchedule() {
axios
.get("http://localhost:5000/api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
I changed my API call to use my actual local IP instead of localhost
function getSchedule() {
axios
.get("http://192.168.X.XX:5000/api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
Nesting my code in the .then block did fix my undefined error even with the bad url. Thank you for that suggestion #Quentin Grisel.
Fixing my url let me test it on different devices.
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.
My case is I have a js web app that I bundle with webpack. The app is deployed to a azure webapp where I like to make use of the Application Settings feature. Particular I like to be able to set the api base url the app should use. To make that work my idea is to place a .php in the root which simple return something like {url: 'api.mypage.com'}. The .php can read the evironment variable and return correct url based on the environment.
Today when I bundle my js it reads the .config with the api url which makes the bundle environment dependent. I have been trying to make a call relative to it self (/api.php) that works fine. The problem is a can't get it working synchronously.
How would you guys solve this? I'm not a js expert but there must be a smart solution fot this. I have been fibbleing with somthing like this:
const dynConfig = {
apiUrl: getApiBaseUrl((param) => {console.log('3. Callback done: ', param); return param;})
}
function getApiBaseUrl(_callBack) {
console.log('1. Enter getApiBaseUrl');
fetch('https://official-joke-api.appspot.com/jokes/programming/random')
.then(response => response.json())
.then( data => {
console.log('2. Data fetched: ', data[0].type);
_callBack(data[0].type);
})
.catch(err => {
console.error(err);
})
}
// This where it fails to "wait"
console.log('4. This should not be undefined: ', dynConfig.apiUrl);
jsfiddle
As per our discussion in the comments here is an implementation with both async/await and promises
Async/Await based
/* Async/Await */
async function async_getApiBaseUrl() {
try {
let res = await (await fetch('https://official-joke-api.appspot.com/jokes/programming/random')).json();
return res[0];
} catch(err) {
console.error(err);
return '';
}
}
async_getApiBaseUrl().then(function(joke){
console.log('async joke:')
console.log(`Got a joke: ${joke.setup}`);
console.log(joke.punchline);
console.log(' ');
});
Promise based
/* Promise */
function promise_getApiBaseUrl(_callBack){
return new Promise(function(resolve, reject) {
fetch('https://official-joke-api.appspot.com/jokes/programming/random')
.then(async function(res) { return await res.json(); })
.then(function (res) {
resolve(res[0]);
})
.catch(function (err) { reject(err) });
});
}
promise_getApiBaseUrl()
.then(function(joke) {
// use your apiBaseUrl
console.log('promise joke:')
console.log(`Got a joke: ${joke.setup}`);
console.log(joke.punchline);
})
.catch(function(err){
console.error(err);
});
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)