I got the following cloud function which works great.
It's listening for an update in the real-time database and update Firestore accordingly.
Everything is fine, except when my user does not exist yet my Firestore database.
This where I need to deal with Unhandled rejection that I see in the Google Cloud Functions log.
So see below, in the shortened version of the function, for my db.collection("users").where("email", "==", email).get() how to stop the function to move forward and prevent the crash.
exports.updateActivities = functions.database.ref("delegates/{userId}/activities").onWrite((event) => {
//Here I set all the needed variable
return rtdb.ref(`delegates/${userId}/email`).once("value", snapshot => {
//Here I'm fine, email is always present.
})
.then(() => {
db.collection("users").where("email", "==", email).get()
//This is where I need to handle when there is not matching value, to stop moving forward.
.then(querySnapshot => {
querySnapshot.forEach(val => {
console.log("Found match in FireStore " + val.id);
firestoreId = val.id;
})
})
.then(() => {
//Here I start my update on Firestore
});
})
});
You should use catch() on every promise that you return from your function that could be rejected. This tells Cloud Functions that you handled the error. The promise returned from catch() will be resolved.
Typically you log the error from catch() so you can see it in the console logs:
return somePromise
.then(() => { /* do your stuff */ }
.catch(error => { console.error(error) })
I used bluebird with suppressUnhandledRejections to get round this issue:
import Promise from 'bluebird';
function pool_push(pool, promise)
{
// In Google Cloud there is a change to crash at `unhandledRejection`.
// The following trick, basically, tells not to emit
// `unhandledRejection` since `.catch` will be attached
// a bit latter.
const tmp = Promise.resolve(promise);
tmp.suppressUnhandledRejections();
pool.items.push(tmp);
}
export default pool_push;
Related
I'm trying to get Vuefire Storage to wait for a file to finish uploading so that I can get the image URL and update Auth and the Firestore. The upload() method is supposed to return a promise, but it doesn't seem to wait for it to resolve before executing the code in .then()
const submit = async () => {
const storage = useFirebaseStorage()
const avatarFileRef = storageRef(storage, fileName.value)
const { url, upload, uploadProgress } = useStorageFile(avatarFileRef)
await upload(file.value[0])!
.then(() => {
if (uploadProgress.value == 1) {
console.log(url.value)
updateAuth(url.value!)
}
})
.catch((error) => {
console.log(error)
// file save error message
})
}
console.log(url.value) is returning null.
I think it is related to the issue i had. I raised it to Eduardo, maintainer of vuefire. Currently there is no way around it. You can see the thread here. It will probably be fixed in the next version.
I've been struggling to understand why my Firebase cloud function isn't working.
I'm deleting a reserved number in a collection called 'anglerNumbers' when a new user has registered and when that users' document has been created. I use this on the client to make sure a reserved number can't be used twice. I'm following the documentation here: https://firebase.google.com/docs/firestore/query-data/queries?authuser=0 (Using Node.js)
But I keep getting the Error: TypeError: snapshot.forEach is not a function
Here's the function:
exports.newUser = functions.firestore.document('users/{userId}')
.onCreate((snap, context) => {
const newUserNumber = snap.data().anglerNumber;
const anglersRef = admin.firestore().collection('anglerNumbers');
const snapshot = anglersRef.where('anglerNumber', '==', newUserNumber).get();
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
doc.delete();
});
})
It does not console log 'No matching documents'. So there are documents but I can't perform the forEach as indicated by the documentation. What am I missing? Thanks!
in this line in your code:
const snapshot = anglersRef.where('anglerNumber', '==', newUserNumber).get();
You assume that get resolves immediately to a snapshot but in fact get() returns a promise that will resolve into a snap shot. You need to wait for this async function.
Either use await if that is possible in your context or use:
anglersRef.where('anglerNumber', '==', newUserNumber).get().then((snapshot)=>{
//do you processing
});
I have created a callable Cloud Function to read data from Firebase and send back the results to the client, however, only "null" is being returned to the client.
exports.user_get = functions.https.onCall((data, context) => {
if (context.auth && data) {
return admin.firestore().doc("users/" + context.auth.uid).get()
.then(function (doc) {
return { doc.data() };
})
.catch(function (error) {
console.log(error);
return error;
})
} return
});
I just reproduced your case connecting from a Cloud Function with a Firestore database and retriving data. As I can see you are trying to access the field in a wrong way when you are using "users/" + context.auth.uid, the method can't find the field so its returning a null value.
I just followed this Quickstart using a server client library documentation to populate a Firestore database and make a Get from it with node.js.
After that i followed this Deploying from GCP Console documentation in order to deploy a HTTP triggered Cloud Function with the following function
exports.helloWorld = (req, res) => {
firestore.collection('users').get()
.then((snapshot) => {
snapshot.forEach((doc) => {
console.log(doc.id, '=>', doc.data().born);
let ans = {
date : doc.data().born
};
res.status(200).send(ans);
});
})
And this is returning the desired field.
You can take a look of my entire example code here
This is because you are making a query from a database firestore, however the cloud support team has made it very cool to protect your applications from data leakages and so in a callable function as the name suggest you can only return data you passed to the same callable function through the data parameter and nothing else. if you try to access a database i suggest you use an onRequest Function and use the endpoint to get you data. that way you not only protect your database but avoid data and memory leakage.
examples of what you can return from a callable function
exports.sayHello = functions.https.onCall((data, context) => {
const name = data.name;
console.log(`hello ${name}`);
return `It was really fun working with you ${name}`;
});
first create a function in your index.js file and accept data through the data parameter but as i said you can only return data you passed through the data parameter.
now call the function
this is in the frontend code (attach an event listener to a button or something and trigger it
/* jsut say hello from firebase */
callButton.addEventListener('click', () => {
const sayHello = firebase.functions().httpsCallable('getAllUsers');
sayHello().then(resutls => {
console.log("users >>> ", resutls);
});
});
you can get your data using an onRequest like so
/* get users */
exports.getAllUsers = functions.https.onRequest((request, response) => {
cors(request, response, () => {
const data = admin.firestore().collection("users");
const users = [];
data.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
users.push(doc.data());
});
return response.status(200).send(users);
});
});
});
using a fetch() in your frontend code to get the response of the new onRequest function you can get the endpoint to the function in your firebase console dashboard.
but not that to hit the endpoint from your frontend code you need to add cors to your firebase cloud functions to allow access to the endpoint.
you can do that by just adding this line to the top of your index.js file of the firebase functions directory
const cors = require("cors")({origin: true});
I detected some recursion on one of the nodes of my realtime database and I want to delete (or set tu null) that specific node. This is my firebase function so far:
exports.cleanForms = functions.https.onRequest((req, res) => {
const parentRef = admin.database().ref("forms");
return parentRef.once('value').then(snapshot => {
snapshot.forEach(function(child) {
admin.database().ref('forms/'+child.key+'/user/forms').set(null);
});
});
});
Basically it should iterate all the records inside the forms node and delete its user/forms property.
But calling that function by going to this url: https://.cloudfunctions.net/cleanForms gives me this error:
Error: could not handle the request
And this is what I see on the logs:
10:47:57.818 PM cleanForms Function execution took 13602 ms, finished
with status: 'connection error'
The forms node has less than 3,000 records but as I mentioned before, it has some recursion on it. I don't know if it is failing due to its size or something related to that.
You are using an HTTPs Cloud Function: therefore you must "send a response to the client at the end" (Watch this official video by Doug Stevenson for more detail: https://youtu.be/7IkUgCLr5oA).
In your case, the "end" of the function will be when ALL of your set() asynchronous operations will be "done". Since the set() method returns a Promise, you have to use Promise.all() (again, watch this official video: https://youtu.be/d9GrysWH1Lc ).
So the following should work (not tested however):
exports.cleanForms = functions.https.onRequest((req, res) => {
const parentRef = admin.database().ref("forms");
parentRef.once('value')
.then(snapshot => {
const promises = [];
snapshot.forEach(child => {
promises.push(admin.database().ref('forms/'+child.key+'/user/forms').set(null));
});
return Promise.all(promises)
.then(results => {
response.send({result: results.length + ' node(s) deleted'});
})
.catch(error => {
response.status(500).send(error);
});
});
I'm writing my first Firebase Cloud Function in TypeScript. The function queries Firestore looking for a document that matches a parameter. The documentation says that when using a promise I need to "finish" the promise so that the function knows when it can complete. How do I do that? Here is my function.
BTW my function only returns the "not found" result right now. And it does work. It does send the response to my client app, but only after the function times out.
export const validateMemberPin = functions.https.onRequest((request, response) => {
console.log('pin: ' + request.query.pin);
const query = admin.firestore().collection('access').where('memberPin', '==', request.query.pin);
return query.get().then((snapshot) => {
if (snapshot.empty)
response.json({'result': 'false'});
});
});
Cloud functions have a NodeJS environment, so to end a function, you just need to add a return statement, in your case just add a return before response.json like so:
return query.get().then((snapshot) => {
if (snapshot.empty)
return response.json({'result': 'false'});
});
However, it would be better if you handle both cases:
return query.get().then((snapshot) => response.json({'result': !snapshot.empty});