I am attempting to generate an invitation code based on when a user adds data to the /households/{house_id}
path using the onValueCreated()
trigger provided by cloud functions.
exports.onCreateHousehold = onValueCreated("/households/{house_id}", (event) => {
// We are not interested in adding a code if the household already exists
if (event.data.child("code").val() != null) {
return null;
}
// A 6 letter/number code translates to 2,176,782,336 possible combinations
const codeLength = 6
return generateCode(codeLength).then((code) => {
log(`Household code generated: ${code}`);
// With the code generated, save the code (this is the problem)
addCode(event.params.house_id, event.data.child("owner").val(), code).then(() => {
log(`The code has been added :)`);
})
}
)
}
)
More specifically, I am generating a code:
/* House invitation code functions */
async function generateCode(codeLength) {
var validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var code = "";
for (i = 0; i <= codeLength; i++) {
code += validCharacters.charAt(parseInt(Math.random() * validCharacters.length));
}
// Check this code doesn't conflict with any existing household codes
const codeMatch = await doesCodeMatch(code);
if (codeMatch) {
// Perform recursion until a unique code is found
generateCode(codeLength);
}
return code;
}
Checking whether this code exists in any existing households:
async function doesCodeMatch(code) {
const householdsRef = db.ref("households");
// Wait until the database has been checked before returning the answer
return await householdsRef
.equalTo(code)
.once("value", (data) => {
return data.exists()
});
}
Then writing the invitation code to the household:
async function addCode(houseID, userID, code) {
const householdData = {
code: code
};
const updates = {};
updates[`/households/${houseID}`] = householdData;
updates[`/users/${userID}/households/${houseID}`] = householdData;
return await update(ref(db), updates);
}
However every time this function runs I get the error RangeError: Maximum call stack size exceeded
.
I have attempted to run addCode()
on its own in the onValueCreated()
function just to see if updating the /households/{house_id}
was causing some loop where onValueCreated()
would be retriggered continuously but this wasn't a problem and the code was added to the path:
exports.onCreateHousehold = onValueCreated("/households/{house_id}", (event) => {
if (event.data.child("code").val() != null) {
return null;
}
// This works fine and F45FD3 gets added
addCode(event.params.house_id, event.data.child("owner").val(), "F45FD3").then(() => {
log(`The code has been added :)`);
})
}
)
This led me to believe then it is something to do with multiple operations being made to the realtime database. Is it possible to make multiple read/writes/updates etc. to the realtime database within a single cloud function trigger, and if so, how could this be done?
addCode
returns a promise, and you need to return that promise (or some other promise derived from it) from the promise chain starting withgenerateCode
. Otherwise, unexpected results may occur. Your recursion ongenerateCode
also doesn't pass along promises from the inner call togenerateCode
. In Cloud Functions, the final promise returned by the function must only become resolved only after all other subordinate promises are also resolved. This is a very common mistake.