Reputation: 8370
I'm getting a weird error. First let me explain my database structure. I'm storing bookable beds for different hotels in my database. The structure is like this:
/beds
|
|- hotel1
| |---- bed1
| |
| |---- bed2
|
|- hotel2
| |---- bed1
| |
| |---- bed2
|
|- hotel3
etc...
A user has the ability to pre-book a bed so that no one else can book it while he's considering to book it or not. There is a timer of 5 minutes for this. To avoid having too many timers, I have a list in my server which holds a list for each hotel which in turn holds an object for each bed of the hotel:
const hotelBedTimeouts = [];
var beds = db.ref('/beds');
// Initialise the bed timeout holder object
beds.once("value", function(snapshot){
var hotels = snapshot.val();
for (var i = 0; i < hotels.length; i++) {
// push empty list to be filled with lists holding individual bed timeouts
if(hotels[i]){
hotelBedTimeouts.push([]);
for(var j = 0; j < hotels[i].length; j++) {
// this list will hold all timeouts for this bed
hotelBedTimeouts[i].push({});
}
} else {
hotelBedTimeouts.push(undefined);
}
}
});
That's how I create the empty timer-holder structure. Then I set a timer whenever a bed is pre-booked by a customer with a firebase function. I also use the function to cancel the timer when the user exits the page:
// Frees a bed after a set amount of time
exports.scheduleFreeBed = functions.database.ref('/beds/{hotelIndex}/{bedIndex}/email').onUpdate( (snapshot, context) => {
var originalEmail = snapshot.after.val();
var hotelIndex = context.params.hotelIndex;
var bedIndex = context.params.bedIndex;
if (originalEmail === -1) {
console.log("Cancelling timeout for chair number " + bedIndex + " with...");
console.log("hotelIndex: " + hotelIndex);
console.log("hotelBedTimeouts[hotelIndex]:");
console.log(hotelBedTimeouts[hotelIndex]);
console.log("hotelBedTimeouts[hotelIndex][bedIndex]");
console.log(hotelBedTimeouts[hotelIndex][bedIndex]);
clearTimeout(hotelBedTimeouts[hotelIndex][bedIndex].timeoutFunc); // clear current timeoutfunc
return 0; // Do nothing
}
console.log("Setting timeout for bed number " + bedIndex + " with...");
console.log("hotelIndex: " + hotelIndex);
console.log("hotelBedTimeouts[hotelIndex]:");
console.log(hotelBedTimeouts[hotelIndex]);
console.log("hotelBedTimeouts[hotelIndex][bedIndex]");
console.log(hotelBedTimeouts[hotelIndex][bedIndex]);
// replace old timeout function
hotelBedTimeouts[hotelIndex][bedIndex].timeoutFunc = setTimeout(function () {
var bedRef = admin.database().ref(`/beds/${hotelIndex}/${bedIndex}`);
bedRef.once("value", function(bedSnap){
var bed = bedSnap.val();
var booked = bed.booked;
if (!booked) {
var currentEmail = bed.email;
// Check if current bed/email is the same as originalEmail
if (currentEmail === originalEmail) {
bedSnap.child("email").ref.set(-1, function() {
console.log("Freed bed");
});
}
}
});
}, 300000); // 5 min timeout
return 0;
});
This works fine most of the time. However, if I pre-book many beds at the same time, there tends to be errors for some of the chairs. Here's how an error looks:
Cancelling timeout for bed number 24 with...
hotelIndex: 1
hotelBedTimeouts[hotelIndex]:
undefined
hotelBedTimeouts[hotelIndex][bedIndex]
TypeError: Cannot read property '24' of undefined
at exports.scheduleFreeBed.functions.database.ref.onUpdate (/user_code/index.js:698:50)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:112:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:82:36)
at /var/tmp/worker/worker.js:728:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
It looks like hotelBedTimeouts[24]
is undefined. This is inexplicable to me for two reasons:
hotelBedTimeouts
with a list for each hotel holding empty objects for beds 1 - 30. hotelBedTimeouts[24]
evaluating to undefined should thus be impossible.What is the reason for this error and how do I fix it?
Upvotes: 1
Views: 58
Reputation: 1540
This means that if your code depends on a certain execution order, you need to make sure that it executes in that order.
The once
function returns a Promise
(more information on Promises here). You could register the scheduleFreeBed
function within the Promise.then()
callback function, so the onUpdate
gets registered after the initialization has completed.
For your example:
// Initialise the bed timeout holder object
beds.once("value", function (snapshot) {
// your existing code...
}).then(() => {
// Frees a bed after a set amount of time
exports.scheduleFreeBed = functions.database.ref('/beds/{hotelIndex}/{bedIndex}/email').onUpdate( (snapshot, context) => {
// your existing code...
});
})
This will make sure that scheduleFreeBed
can only be triggered after your initialization has finished.
onUpdate
will be ignored if the data is changed during the initialization!Since the above apparently doesn't work because obviously asynchronous exports registration was a horrible idea, the following snippet should be an alternative with the added benefit of making sure that the scheduling will be in FIFO order in addition to making sure that it executes only after being properly initialized. Also the prior downside of triggering during initialization being ignored will be avoided through this change:
// Initialize the bed timeout holder object
var initPromise = beds.once("value", function (snapshot) {
// your existing code...
});
// Frees a bed after a set amount of time
exports.scheduleFreeBed = functions.database.ref('/beds/{hotelIndex}/{bedIndex}/email').onUpdate( (snapshot, context) =>
// make sure the scheduling happens after the initialization and in order
// since his chaining doubles as a queue
initPromise = initPromise.then(() => {
// your existing code...
})
);
Upvotes: 2