I have a cloud function triggered by an http request which intends to do the following:
Therefore I need something that waits for the looping of (2) and (3) and then performs a batch operation.
Below is the code that I have at the moment and is kind of working when I test the function locally. However I cannot deploy it to Firebase since it has promises errors such as "every then must return a promise" and "avoid nesting promises".
exports.finishEvents = functions.https.onRequest((req, res) => {
const eventsRef = admin.firestore().collection('events');
var currentTime = new Date().getTime();
var currentTimeMinus1h = currentTime - 3600000;
console.log('----- finishEvents started -----')
const queryRef = eventsRef.where('finished', '==', false).where('date', '<=', new Date(currentTimeMinus1h)).get().then(function(querySnapshot){
if (querySnapshot.size > 0) {
querySnapshot.forEach(function(doc) {
var owner_id = doc.data().owner_id;
var event_id = doc.id;
console.log(owner_id, event_id);
var userEventOwnerGoingRef = admin.firestore().collection("user_events").doc(owner_id).collection('going').doc(event_id);
userEventOwnerGoingRef.get().then(doc2 => {
if (!doc2.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc2.data());
var goingIds = doc.data().going_ids;
console.log('GOING IDS', goingIds);
var batch = admin.firestore().batch();
for (var userId in goingIds) {
if (goingIds.hasOwnProperty(userId)) {
console.log(userId + " -> " + goingIds[userId]);
var eventRef = admin.firestore().collection("events").doc(event_id);
var userEventGoingRef = admin.firestore().collection("user_events").doc(userId).collection('going').doc(doc2.id);
var userEventAttendedRef = admin.firestore().collection("user_events").doc(userId).collection('attended').doc(doc2.id);
batch.set(userEventAttendedRef, doc2.data());
batch.delete(userEventGoingRef)
if (userId == doc2.data().owner_id) batch.update(eventRef, {finished: true});
}
}
batch.commit().then(function () {
return res.status(200).send("Done.");
});
}
})
.catch(err => {
console.log('Error getting userEventOwnerGoingRef', err);
return res.status(200).send("Finished.");
});
});
} else {
console.log("No events found");
return res.status(200).send("Finished.");
}
})
.catch(err => {
console.log('Error getting events', err);
return res.status(200).send("Finished.");
});
});
When I test it locally, even though the job is completed, I get an error stating that
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Can't set headers after they are sent.
I can see that I am sending the result for each document of the original query, and I would only need to send the result once to finish the cloud function.
I guess I need to return promises and then after steps (2) and (3) are completed perform my batch transaction of everything. However is the first time I am using javascript and I am struggling with this. Any help would be appreciated.
The Unhandled promise rejection
error is encountered when your HTTPS function has sent a response but forgot to return a promise that resolves before the timeout limit has been reached. This means that you aren't returning all of your promises in the HTTPS function. Your code should look something like this:
exports.finishEvents = functions.https.onRequest((req, res) => {
const eventsRef = admin.firestore().collection('events')
const currentTime = new Date().getTime()
const currentTimeMinus1h = currentTime - 3600000
console.log('----- finishEvents started -----')
const queryRef = eventsRef
.where('finished', '==', false)
.where('date', '<=', new Date(currentTimeMinus1h))
return queryRef.get().then((querySnapshot) => {
// Use Promise.all with snapshot.docs.map to combine+return Promise context
return Promise.all(querySnapshot.docs.map((doc) => {
const owner_id = doc.get('owner_id')
const event_id = doc.id
console.log(owner_id, event_id)
const userEventOwnerGoingRef = admin.firestore()
.collection("user_events").doc(owner_id)
.collection('going').doc(event_id)
return userEventOwnerGoingRef.get().then((doc2) => {
if (!doc2.exists) {
console.log('No such document!')
return
} else {
console.log('Document data:', doc2.data())
const goingIds = doc.get('going_ids')
console.log('GOING IDS', goingIds)
const batch = admin.firestore().batch()
for (const userId in goingIds) {
if (goingIds.hasOwnProperty(userId)) {
console.log(userId + " -> " + goingIds[userId])
const eventRef = admin.firestore().collection("events").doc(event_id)
const userEventGoingRef = admin.firestore()
.collection("user_events").doc(userId).collection('going').doc(doc2.id)
const userEventAttendedRef = admin.firestore()
.collection("user_events").doc(userId).collection('attended').doc(doc2.id)
batch.set(userEventAttendedRef, doc2.data())
batch.delete(userEventGoingRef)
if (userId == doc2.get('owner_id')) {
batch.update(eventRef, {finished: true})
}
}
}
return batch.commit()
}
})
}))
})
.then(() => {
return res.status(200).send('Done.')
})
.catch((err) => {
console.error(err)
return res.status(200).send('Finished.')
})
})
The important thing to take from this is to not strand any of your promises. Always keep a handle on them whether it be by adding them to an array and waiting for them all to resolve/reject, or by returning them from their scopes/functions. I hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With