I'm developing a firebase scheduled function that updates my Firestore collection key-values based on some condition specified in the function. I want this scheduled function to be called every 5 seconds, but as I could understand from the firebase documents that the minimum granularity is 1 minute.
I'm thinking of using a setInterval() where each interval is of 5 seconds and executes the above-discussed function, but I'm not comfortable with such method, as I know this is not the correct way to do it and I may also end up incurring additional charges.
Any idea how to achieve this in a proper way? Thanks in advance.
The immediate and easiest solution I could think of was to use Javascript Interval Timers.
Please use the below given sample code if needed,
const intervalId = setInterval(() => {
// call your main function here
}, MAIN_FUNCTION_INTERVAL * 1000);
// Clear timers before next scheduled cycle i.e. after every 1 minute
await new Promise((resolve) => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, (60 - MAIN_FUNCTION_INTERVAL) * 1000);
});
You need to call it multiple times in one schedule event.
const MAX_EXECUTE_TIME =3
const SLEEP_TIME = 18
exports.scheduledFunction = functions.pubsub.schedule('every 1 minutes').onRun((context) => {
for await (const executeTime of [...Array(MAX_EXECUTE_TIME)].map(
(_, i) => i
)) {
console.log("every 20 seconds");
await sleep(SLEEP_TIME);
}
});
Related
Is there any way to wait for a Cloud Function, that was triggered by a Firestore document write, to finish?
Context:
My app has groups. Owners can invite other users to a group via an invite code. Users can write themselves as member of a group if they have the right invite code. They do this by writing the groups/{groupId}/members/{userId} document that contains their profile info.
To make reading more efficient, this info is copied to array members in the groups/{groupId} document by a Cloud Function.
The Cloud Function that does that is triggered by the document write. It is usually finished after a couple of seconds, but there's no predictable execution time and it might take a bit longer if it is a cold start.
After the user has joined the group, I forward them to the groups view in my app which reads the group document. In order for the view to render correctly, the membership info needs to be available. So I would like to forward AFTER the Cloud Function has finished.
I found no way to track the execution of a Cloud Function that was triggered by a Firestore document write.
A fellow developer recommended to just poll the groups/{groupId} document until the info is written and then proceed but this doesn't seem like a clean solution to me.
Any ideas how this could be done better?
Is it possible to get a promise that resolves after the Cloud Function has finished? Is there a way to combine a Firestore document write and a Cloud Function execution into one transaction?
Thanks for the hints, I came up with the following ways to deal with the problem. The approach depends on if/when the user is allowed to read a document:
A) User is member and leaves the group > at the start of the transaction they are allowed to read the group > the moment they can't read anymore confirms that the membership was successfully revoked:
async function leaveGroup (groupId) {
await deleteDoc(doc(db, 'groups', groupId, 'members', auth.currentUser.uid))
// Cloud Function removes the membership info
// from the group doc...
await new Promise((resolve) => {
const unsubscribeFromSnapshot = onSnapshot(
doc(db, 'groups', groupId),
() => { }, // success callback
() => { // error callback
// membership info is not in the group anymore
// > user can't read the doc anymore
// > transaction was successful
// read access was revoked > transaction was successful:
unsubscribeFromSnapshot()
resolve()
}
)
})
}
B) User is not a member and wants to join the group > at the start of the transaction they are allowed to read the group > the moment they can read the group confirms that the membership was successfully confirmed (this is a simplified version that does not check the invite code):
async function joinGroup (groupId) {
try {
await setDoc(
doc(db, 'groups', groupId, 'members', auth.currentUser.uid),
{
userId: auth.currentUser.uid,
userDisplayName: auth.currentUser.displayName
}
)
// Cloud Function adds the membership
// information to the group doc ...
await new Promise((resolve) => {
let maxRetries = 10
const interval = setInterval(async () => {
try {
const docSnap = await getDoc(doc(db, 'groups', groupId))
if (docSnap.data().members.includes(auth.currentUser.uid)) {
// membership info is in the group doc
// > transaction was successful
clearInterval(interval)
resolve()
}
} catch (error) {
if (maxRetries < 1) {
clearInterval(interval)
}
}
maxRetries--
}, 2000)
})
}
Note: I went with polling here, but similar to what #samthecodingman suggested, another solution could be that the Cloud Function confirms the membership by writing back to the members document (which the user can always read) and you listen to snapshot changes on this document.
C) Most straightforward way: someone else (the group owner) removes a member from the group > they have read access through the whole transaction > directly listen to snapshot changes:
async function endMembership (groupId, userId) {
await deleteDoc(doc(db, 'groups', groupId, 'members', userId))
// Cloud Function removes the membership info
// from the group doc...
await new Promise((resolve) => {
const unsubscribe = onSnapshot(doc(db, 'groups', groupId), (doc) => {
if (!doc.data().members.includes(userId)) {
// membership info is not in the group doc anymore
// > transaction was successful
unsubscribe()
resolve()
}
})
})
}
In any case you should do proper error handling that covers other causes. I left them out to demonstrate how to use the error handlers when waiting for gaining/loosing read access.
I have a timer function on firebase functions.
My code below
exports.timecontroller = functions.region('europe-west1').firestore.document("DigitalTargets/{digitalTargetID}").onCreate((snap, context) => {
const id = snap.id
const date = new Date(snap.data().endDate.toDate())
var countDownDate = date.getTime();
var myfunc = setInterval(async function () {
var now = new Date().getTime();
var timeleft = countDownDate - now;
db.collection('DigitalTargets').doc(snap.id).get().then(a => {
if (!a.data().isClaimed) {
console.log(timeleft)
if (timeleft < 0) {
db.collection("DigitalTargets").doc(id).update({ isActive: false })
clearInterval(myfunc);
}
}
else {
clearInterval(myfunc);
}
})
}, 1000);
return true;
})
My problem is, when i create a doc it starts the count. I can see on log screen. But after 10 min it stops working. no more logs shown. After expire time its not deactivating doc
What i need:
I placing targets on map and they have end time. I just want to set active false after timer finished.
is there any limitation on firebase functions? if theres ill check every 5 minutes with scheduled function.
From the docs:
The maximum value for timeoutSeconds is 540, or 9 minutes.
https://firebase.google.com/docs/functions/manage-functions
As mentioned, you could use a scheduled function:
exports.myCrontabFunction = functions.pubsub.schedule("every 5 minutes")
.timeZone("Europe/Berlin")
.onRun(async (context) => { ... } );
Sounds like you are using an Event Driven Function that starts when you create a doc.
As per this public documentation you can see that Even Driven Functions times out after 10 mins.
The alternative could be to have that event driven function to make an http request to another function with a higer time out of 60 minutes.
I'd like to test performance in my firebase app. Is there a way to import thousands of dummy records into firestore for performance testing?
You can use a HTTP Cloud Function to pass the number of dummy values you want to create:
This is an example to add Users, you can adapt it according to your needs. Running the writes individually and collecting with a Promise.all() allows then to run in parallel, potentially speeding up execution.
exports.addDummyUsers = functions.https.onRequest((request, response) => {
let dbb = admin.firestore();
let counter = request.query.counter;
const promises = []
for (let i = 0; i < counter; i++) {
promises.push(
dbb.collection('Users').doc('Lone' + i).set({
email: 'Dummy',
name: 'Dummy',
phoneNumber: 'Dummy'
})
)
}
return Promise.all(promises)
.then(resultsArray => {
response.send(counter + ' Dummy Values Created!');
})
.catch(error => {
});
});
You can also take a look into the official video.
You can write code to add the dummy documents that you want. There isn't any off-the-shelf tool that will automatically generate exactly what you need.
Suppose i created a document in cloud firestore and i want that data to be changed after 5 mins of data creation.
How can i do that job in dart(framework:flutter).
thanks in advance
If you are looking to update your Firestore document at a specific time frequency, you may consider scheduled Cloud Functions. This method of updating documents creates a Google Cloud Pub/Sub topic and Cloud Scheduler to trigger events on that topic, which ensures that your function runs on the desired schedule.
You can use Timer for update data after specific time.
Timer _timer;
int _start = 10;
void startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(
() {
if (your logic) {
//Your code
} else {
//Your code
}
},
),
);
}
I am not sure about this question if this can be implemented or not.
I am using node.js with express.js and MySQL database.
I have a few records in MySQL database. These records are updating continues.
So, suppose I fetch some records from MySQL and start operations on each record with Promise.all using demoFunction function which is returned promise.
In this function, I am trying to check for new records in MySQL database. If I got new records then I want to push this new record's operation into current Promise.all queue. Is this possible? If not possible then how can I achieve this goal with continues execution?
So, my code is like,
const demoFunction = (arg1, arg2) => {
checkForNewData();
return new Promise((resolve, reject) => {
// Rest of my code is here for this function
// This function will be take around 5 to 10 mins
});
};
const dataFromDatabase = "Here i'm getting some data into array of object from SQL database";
let allPromises = dataFromDatabase.map((obj) => demoFunction(obj.arg1, obj.arg1));
const checkForNewData = () => {
const newDataFromDatabase = "Here i'm getting some new data into array of object from SQL database";
for (let i = 0; i < newDataFromDatabase.length; i++) {
allPromises.push(demoFunction(newDataFromDatabase[i].arg1, newDataFromDatabase[i].arg2));
}
};
return Promise.all(allPromises)
.then(() => {
// response
})
.catch((e) => {
console.log(e);
})
In this function, I am trying to check for new records in MySQL database. If I got new records then I want to push this new record's operation into current Promise.all queue. Is this possible?
Nope, Promise.all takes a finite and set number of promises and waits for all of them to complete.
If not possible then how can I achieve this goal with continues execution?
Well, a promise is just a value - if you have a promise for something then execution has already started somewhere else. You can always execute a second .all but what happens if records were added in the meantime?
It's fine to do:
Promise.all(allPromises).then(() => Promise.all(allPromises)).then(() => {
});
But at that point you're better off just waiting for the checkNewData call to finish before calling the Promise.all since otherwise you're introducing a race between checkAllData and the Promise.all
A promise is a "one time" thing, consider using an async iterator if you want to process results (note, this requires Node 12):
async function* getRecordData() {
for await(const item in getPromisesOfInitDataFromDatabase()) {
yield item; // or process it
}
while(true) { // or how often you want
for await(const item of getNewDastaFromDatabase()) {
yield item; // or process it
}
await sleep(3000); // or some sleep timeout to not constantly poll
}
}
Then elsewhere:
(async () => {
for await(const item of getRecordData()) {
// items are available here one by one, including new items in the database
}
})();