How do I "finish" a cloud function - firebase

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});

Related

Is return value important in Firebase Cloud Functions

I am writing the Firebase Could Functions with TypeScript and the following is a simple method to update a document.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);
export const handleTestData = functions.firestore.document('test/{docID}').onCreate(async (snap, context) => {
const data = snap.data();
if (data) {
try {
await admin.firestore().doc('test1/' + context.params.docID + '/').update({duplicate : true});
} catch (error) {}
}
});
In this method, the promise is handled by async await and there is no return statement and it's working fine. Most of the examples/tutorials I have seen always have a return statement in each method.
Is there any impact/difference I don't return anything in Firebase Cloud Functions? If I should return something, can I return null?
Is return value important in Firebase Cloud Functions?
Yes, it is really key, in a Cloud Function which performs asynchronous processing (also known as "background functions") to return a JavaScript promise when all the asynchronous processing is complete, as explained in the documentation.
Doing so is important for two main reasons (excerpts from the doc):
You make sure that the Cloud Functions instance running your Cloud Function does not shut down before your function successfully reaches its terminating condition or state.
You can avoid excessive charges from Cloud Functions that run for too long or loop infinitely.
Why is your Cloud Function running correctly even if you don't return a Promise?
Normally your Cloud Function should be terminated before the asynchronous operations are completed, because you don't return a Promise and therefore indicate to the Cloud Functions platform that it can terminate the Cloud Functions instance running the Cloud Function.
But sometimes, the Cloud Functions platform does not terminate the Function immediately and the asynchronous operations can be completed. This is not at all guaranteed and totally out of your control.
Experience has shown that for short asynchronous operations this last case happens quite often and the developer thinks that everything is ok. But, all of sudden, one day, the Cloud Function does not work... and sometimes it does work: The developer is facing an "erratic" behaviour without any clear logic, making things very difficult to debug. You will find a lot of questions in Stack Overflow that illustrate this situation.
So concretely, in your case you can adapt your code like:
export const handleTestData = functions.firestore.document('test/{docID}').onCreate(async (snap, context) => {
const data = snap.data();
if (data) {
try {
// See the return below: we return the Promise returned by update()
return admin.firestore().doc('test1/' + context.params.docID + '/').update({duplicate : true});
} catch (error) {
return null; // <- See the return
}
} else {
return null; // <- See the return
}
});
or like
export const handleTestData = functions.firestore.document('test/{docID}').onCreate(async (snap, context) => {
const data = snap.data();
if (data) {
try {
await admin.firestore().doc('test1/' + context.params.docID + '/').update({duplicate : true});
return null; // <- See the return
} catch (error) {
return null; // <- See the return
}
} else {
return null; // <- See the return
}
});
Returning null (or true, or 1...) is valid since an async function always returns a Promise.

Firebase Cloud Functions: TypeError snapshot.forEach is not a function

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
});

Using a callable function to send data back to the client from Firebase

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});

how to run firebase query inside a function in node js

I have a function which should query firebase db and return a result.
function verifyToken(token)
{
var androidId = 'xxxxx';
admin.database(dbDEV).ref('profiles').orderByChild('androidId').equalTo(androidId).on('value',(snapshot)=>{
console.log(snapshot.val());
return snapshot.val();
});
}
I am using firebase functions for this . so the result is getting logged in firebase logs but i am not getting and return value while executing the function.
Two things:
Use once() instead of on() to query data a single time. on() establishes a listener that listens forever, until you remove the listener.
Realtime Database queries are all asynchronous, meaning they return immediately, and the callback function you provide is invoked some time later with the results. You can't simply return the results from the callback in order to return those results from the enclosing function. If you want verifyToken to yield query results to the caller, you should return a promise that resolves with the data.
You can start by using promises. For example:
function verifyToken(token) {
return new Promise(resolve => {
var androidId = 'xxxxx';
admin.database(dbDEV).ref('profiles').orderByChild('androidId').equalTo(androidId).on('value',(snapshot)=>{
console.log(snapshot.val());
resolve(snapshot.val());
});
});
}
And when you need the result:
verifyToken(token).then(result => {
... do stuff
});
To improve on this, you can use an async function. For example:
async function foo() {
const result = await verifyToken(token);
console.log(result);
}

Unhandled Rejection in Google Cloud Functions

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;

Resources