0

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?

2
  • 1
    I don't know if this is the root cause, but your addCode returns a promise, and you need to return that promise (or some other promise derived from it) from the promise chain starting with generateCode. Otherwise, unexpected results may occur. Your recursion on generateCode also doesn't pass along promises from the inner call to generateCode. 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. Commented May 13, 2024 at 19:35
  • @DougStevenson Thanks Doug your advice helped me find the issue! :)
    – Duckington
    Commented May 13, 2024 at 22:18

1 Answer 1

0

@Doug Stevenson led me onto the right idea and this was indeed a promise issue. doesCodeMatch() was passing back a promise that I incorrectly assumed was of a boolean type but it was actually a data snapshot. Therefore if (codeMatch) {} was always returning true and generateCode(codeLength) was being called on indefinitely causing a stack overflow! The code was changed to as follows and both generateCode() and addCode now executes:

if (codeMatch.exists()) {
    // Perform recursion until a unique code is found
    code = generateCode(codeLength);
}

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.