Cloud Function keeps running - firebase

We have a Firebase function which removes items after 24 hours. The function works, however, the web page linking to the function keeps running so I guess the function is in an endless loop. Is there any way to fix this issue?
This is our function:
exports.delete = functions.https.onRequest((req, res) => {
const currentTime = new Date().getTime();
const dayAgo = currentTime - 86400000;
ref.child('testlijst')
.orderByChild('tijd')
.endAt(dayAgo)
.once('value')
.then(snap => {
snap.forEach(c => {
c.ref.remove();
})
});
});

You are required to send a response to the client in order to terminate an HTTPS type function without timing out. You should do that only after the work you're doing in the function is complete. This means you'll need to learn how to use promises.
You will need to collect all the promises returned by c.ref.remove() and wait for all of them to complete before terminating the function with a response to the client:
ref.child('testlijst')
.orderByChild('tijd')
.endAt(dayAgo)
.once('value')
.then(snap => {
const promises = [];
snap.forEach(c => {
const p = c.ref.remove();
promises.push(p);
})
Promise.all(promises).then(result => {
res.send("done");
});
});
});

Related

Trouble reading data in Firebase Cloud Function

Trying to read a pushToken from a given user in the users collection (after an update operation on another collection) returns undefined
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
})
.then(r => {
getToken(idTo).then(token => {
// sendMsg...
})
}).catch(updateErr => {
console.log("updateErr: " + updateErr)
})
async function getToken(id) {
let response = "getTokenResponse"
console.log("id in getToken: " + id)
return db.collection('users').doc(id).get()
.then(user => {
console.log("user in getToken: " + user.data())
response = user.data().pushToken
})
.catch(e => {
console.log("error get userToken: " + e)
response = e
});
return response
}
return null
});
And this is from the FB console log:
-1:43:33.906 AM Function execution started
-1:43:36.799 AM Function execution took 2894 ms, finished with status: 'ok'
-1:43:43.797 AM id in getToken: Fm1RwJaVfmZoSgNEFHq4sbBgoEh1
-1:43:49.196 AM user in getToken: undefined
-1:43:49.196 AM error get userToken: TypeError: Cannot read property 'pushToken' of undefined
-1:43:49.196 AM returned token: undefined
And we can see in this screenshot from the db that the doc does exist:
Hope someone can point me to what I'm doing wrong here.
added screenshot of second example of #Renaud as deployed:
As Doug wrote in his comment, you need to "return a promise from the top level function that resolves when all the async work is complete". He also explains that very well in the official video series: https://firebase.google.com/docs/functions/video-series/ (in particular the 3 videos titled "Learn JavaScript Promises"). You should definitely watch them, highly recommended!
So, the following modifications to your code should work (untested):
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite(async (snap, context) => { // <- note the async keyword
try {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
await db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
});
const userToSnapshot = await db.collection('users').doc(idTo).get();
const token = userToSnapshot.data().pushToken;
await sendMsg(token); // <- Here you should take extra care to correctly deal with the asynchronous character of the sendMsg operation
return null; // <-- This return is key, in order to indicate to the Cloud Function platform that all the asynchronous work is done
} catch (error) {
console.log(error);
return null;
}
});
Since you use an async function in your code, I've used the async/await syntax but we could very well write it by chaining the promises with the then() method, as shown below.
Also, I am not sure, in your case, that it adds any value to put the code that gets the token in a function (unless you want to call it from other Cloud Functions but then you should move it out of the addDenuncia Cloud Function). That's why it has been replaced by two lines of code within the main try block.
Version with chaining promises via the then() method
In this version we chain the different promises returned by the asynchronous methods with the then() method. Compared to the async/await version above, it shows very clearly what means "to return a promise from the top level function that resolves when all the asynchronous work is complete".
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => { // <- no more async keyword
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
return db.collection('Classificados').doc(classificadoId) // <- we return a promise from the top level function
.update({
aprovado: false
})
.then(() => {
return db.collection('users').doc(idTo).get();
})
.then(userToSnapshot => {
if {!userToSnapshot.exists) {
throw new Error('No document for the idTo user');
}
const token = userToSnapshot.data().pushToken;
return sendMsg(token); // Again, here we make the assumption that sendMsg is an asynchronous function
})
.catch(error => {
console.log(error);
return null;
})
});

await response of image upload before continue function

So I am working on a upload function for multiple images in an array. After a lot of struggling I have finally got my upload function to work and the images are showing up in the Firebase Database. However I have yet to find out a working way to make sure my upload function completes before continuing.
Below is the part were I am calling the upload function and try to store the response in uploadurl, the uploadurl variable is later used in the dispatch function to store the url with other data.
try {
uploadurl = await uploadImages()
address = await getAddress(selectedLocation)
console.log(uploadurl)
if (!uploadurl.lenght) {
Alert.alert('Upload error', 'Something went wrong uploading the photo, plase try again', [
{ text: 'Okay' }
]);
setIsLoading(true);
return;
}
dispatch(
So the image upload function is below. This works to the point that the images are uploaded, however the .then call to get the DownloadURL is not started correctly and the .then images also is not working.
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
let imagesArray = [];
try {
Promise.all(photos)
.then(photoarray => {
console.log('all responses are resolved succesfully')
for (let photo of photoarray) {
let file = photo.data;
const path = "Img_" + uuid.v4();
const ref = firebase
.storage()
.ref(`/${uid}/${path}`);
var metadata = {
contentType: 'image/jpeg',
};
ref.putString(file, 'base64', metadata).then(() => {
ref
.getDownloadURL()
.then(images => {
imagesArray.push({
uri: images
});
console.log("Out-imgArray", imagesArray);
})
})
};
return imagesArray
})
} catch (e) {
console.error(e);
}
};
So I want to return the imagesArray, AFTER, all the photos are uploaded. So the imagesArray is then set as uploadURL in the first function? After all images URL are set in imagesArray and passed to uploadURL, only then my dispatch function to upload the rest of the data should continue. How can I make sure this is happening as expected?
I have changed this so many times now because I keep getting send to different ways of doing this that I am completely at a loss how to continue now :(
Most of your uploadImages() code was correct, however in many places you didn't return the promise from each asynchronous action.
Quick sidestep: Handling many promises
When working with lots of asynchronous tasks based on an array, it is advised to map() the array to an array of Promises rather than use a for loop. This allows you to build an array of promises that can be fed to Promise.all() without the need to initialise and push to another array.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry();
});
Promise.all(arrayOfPromises)
.then((resultsOfPromises) => {
console.log('All promises resolved successfully');
})
.catch((err) => {
// an error in one of the promises occurred
console.error(err);
})
The above snippet will fail if any of the contained promises fail. To silently ignore individual errors or defer them to handle later, you just add a catch() inside the mapped array step.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry()
.catch(err => ({hasError: true, error: err})); // silently ignore errors for processing later
});
Updated uploadImages() code
Updating your code with these changes, gives the following result:
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
// CHANGED: removed 'let imagesArray = [];', no longer needed
return Promise.all(photos) // CHANGED: return the promise chain
.then(photoarray => {
console.log('all responses are resolved successfully');
// take each photo, upload it and then return it's download URL
return Promise.all(photoarray.map((photo) => { // CHANGED: used Promise.all(someArray.map(...)) idiom
let file = photo.data;
const path = "Img_" + uuid.v4();
const storageRef = firebase // CHANGED: renamed 'ref' to 'storageRef'
.storage()
.ref(`/${uid}/${path}`);
let metadata = {
contentType: 'image/jpeg',
};
// upload current photo and get it's download URL
return storageRef.putString(file, 'base64', metadata) // CHANGED: return the promise chain
.then(() => {
console.log(`${path} was uploaded successfully.`);
return storageRef.getDownloadURL() // CHANGED: return the promise chain
.then(fileUrl => ({uri: fileUrl}));
});
}));
})
.then((imagesArray) => { // These lines can
console.log("Out-imgArray: ", imagesArray) // safely be removed.
return imagesArray; // They are just
}) // for logging.
.catch((err) => {
console.error(err);
});
};

Google Translate API and Firebase Firestore are killing each other

We're trying to write a Google Cloud Function that gets a translation from Google Translate API, and then write the results to our Firebase Firestore database. Each works alone, but together nothing works. In other words, we can get a translation from Google Translate. We can write data to Firestore. But if we try to do both, we don't get a translation back from Google Translate, and nothing is written to Firebase. We get no error messages. We've tried the code with async await and with promises. Here's the code with promises:
exports.Google_EStranslateEN = functions.firestore.document('Users/{userID}/Spanish/Translation_Request').onUpdate((change, context) => {
if (change.after.data().word != undefined) {
const {Translate} = require('#google-cloud/translate');
const projectId = 'myProject-cd99d';
const translate = new Translate({
projectId: projectId,
});
// The text to translate
const text = change.after.data().word;
// The target language
const target = 'en';
let translationArray = []; // clear translation array
translate.translate(text, target)
.then(results => {
translation = results[0];
translationArray.push(translation);
try {
// write translation to dictionary
admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(text).collection('Translations').doc('English').set({
'translationArray': translationArray,
'language': 'en',
'longLanguage': 'English'
})
.then(function() {
console.log("Translation written");
})
.catch(function(error) {
console.error(error);
});
} catch (error) {
console.error(error);
}
})
.catch(error => {
console.error('ERROR:', error);
});
}
});
Here's the same code with async await:
exports.Google_EStranslateEN = functions.firestore.document('Users/{userID}/Spanish/Translation_Request').onUpdate((change, context) => { // triggers when browser writes a request word to the database
if (change.after.data().word != undefined) {
async function getTranslation() {
try {
let translationArray = []; // clear translation array
const {Translate} = require('#google-cloud/translate');
const projectId = 'myProject-cd99d';
const translate = new Translate({
projectId: projectId,
});
// The text to translate
const text = change.after.data().word;
const options = {
to: 'en',
from: 'es',
format: 'text'
}
let [translations] = await translate.translate(text, options);
translations = Array.isArray(translations) ? translations : [translations]; // If translations is an array, leave it alone; if not, put it in an array
translationArray.push(translations[0]);
await admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(text).collection('Translations').doc('English').set({
'translationArray': translationArray,
'language': 'en',
'longLanguage': 'English'
})
.then(function() {
console.log("Translation written");
})
.catch(function(error) {
console.error(error);
});
// };
} catch (error) {
console.error(error);
}
} // close getTranslation
getTranslation();
}
});
You're not returning a promise that's resolved when all the async work is complete. If you don't do that, Cloud Functions assumes that all your work is complete, and will clamp down on all resources, and any pending work will be shut down.
The promise returned by translate.translate().then().catch() is being ignored. Your nested call to admin.firestore()...set() has a similar problem. It's not sufficient to just call then() and catch() on every promise because then() and catch() both return yet another promise.
You're also unnecessarily mixing usage of try/catch with catch() on the promise. You don't need both strategies for error handling, just one or the other.
When you used await in your second example, you forced JavaScript to pause until the async work represented by the promise returned by set() was complete. This allowed your function to return only after all the work was finished, which is why it worked correctly.
You might be helped by watching my video series on use of promises and async/await in Cloud Functions. Proper handling of promises is crucial to creating a correctly working function.

how to get data from a http request with firebase cloud functions?

I create a firebase function, in the cloud the stuff exists, in the code i return this stuff but wehen i try get the data in a http request don't charge anything.
This is my Function in firebase functions:
exports.autentifyuser = functions.https.onRequest((req, res) => {
var stuff = [];
var db = admin.firestore();
const key=req.query.ids;
db.collection("/usuariosdinny/").where("key","==",key).get().then(snapshot => {
snapshot.forEach(doc => {
var newelement = {
"id": doc.id,
"nombre": doc.data().nombre,
"foto": doc.data().foto,
"descripcion":doc.data().descripcion,
"edad":doc.data().edad,
"key":doc.data().key,
"sexo":doc.data().sexo,
"telefono":doc.data().telefono,
"tipo":doc.data().tipo
}
stuff = stuff.concat(newelement);
});
console.log(stuff);
res.send(stuff);
return stuff;
}).catch(reason => {
res.send(reason);
console.log(reason);
return reason;
})
});
this is the call:
let params: URLSearchParams = new URLSearchParams();
params.set('ids',this.ids);
let requestOptions = new RequestOptions();
requestOptions.search = params;
this.http.get('https://us-central1-dinnyud-9b9c8.cloudfunctions.net/autentifyuser',requestOptions).toPromise().then(response=>console.log(response.json()) && this.navCtrl.push(TabsControllerPage) && loader.dismiss())
.catch(error=>alert2.present()&& loader.dismiss());
I need the response.json() and get the "Tipo" data. Thanks for your help.
Since the call to get data from the database happens asynchronously, you need to tell Cloud Functions that you're not done yet. Otherwise it may terminate your function as soon as the last } has executed, which is well before the data is retrieved from Firestore.
To tell Cloud Functions about this, you have to return a promise. Luckily this is quite easy, since get already returns a promise.
return db.collection("/usuariosdinny/").where("key","==",key).get().then(snapshot => {
snapshot.forEach(doc => {

Asynchronous https firebase functions

Should HTTPS functions return asynchronous promises like realtime functions have to?
We haven't been returning in HTTPS functions (just using res.status.send etc), and it looks like firebase/function-samples aren't either. But the documentation is slightly ambiguous https://firebase.google.com/docs/functions/terminate-functions .
This works now in the latest Firebase:
exports.asyncFunction = functions.https.onRequest(async (request, response) => {
const result = await someAsyncFunction();
response.send(result);
});
HTTP functions currently do not respect returned promises - they require a sent result in order to terminate normally. If an HTTP function doesn't send a result, it will time out.
All other types of functions require a returned promise in order to wait for asynchronous work to fully complete.
If you don't have any async work to wait for, you can just return immediately.
These are the three cases outlined in the docs.
After much looking around , this is implementation with a Promise worked for me to return a value from a Google Cloud Function where the function needs to make a third-party asynchronous call :
exports.getSomeAccessToken = functions.https.onCall((data, context) => {
var dataStr = JSON.stringify(data, null, '\t');
console.log('ENTER [getSomeAccessToken], got dataStr: ' + dataStr);
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, gatewayResponse) {
var result = {
clientToken: gatewayResponse.clientToken
};
var resultStr = JSON.stringify(result, null, '\t');
console.log("resultStr : " + resultStr);
resolve(result);
});
});
});
Your cloud functions should return"end" with either of the following
res.redirect(), res.send(), or res.end()
What they mean by returning promises, is lets imagine you have a cloud function that updated a node in your realtime database, you would like to complete that work before responding to the HTTP request.
Example code
let RemoveSomething = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Remove something
DoDatabaseWork()
.then(function (result) {
res.status(200).send();
})
.catch(function (err) {
console.error(err);
res.status(501).send();
});
});
});
Update: Added DoDatabaseWork example.
const DoDatabaseWork = function () {
return new Promise(function (resolve, reject) {
// Remove SomeNode
admin.database().ref('/someNode/').remove()
.then(function (result) {
resolve();
})
.catch(function (err) {
console.error(err);
reject();
});
});
}

Resources