9
votes

I'm calling a Firebase callable function, and returning the promise as specifed by Firebase, but I get this error:

Unhandled error RangeError: Maximum call stack size exceeded

Here's the code:

exports.getProductInfo = functions.https.onCall((data, context) => {
  // Message text passed from the client.
  const product = data.product;
  // Authentication / user information is automatically added to the request.
  // const uid = context.auth.uid;

  // Get the current Server Timestamp
  var ts = String(Date.now());
  var key, snap, node;

  // Get the price of the specified product
  return database.ref('/products/' + product)
                 .orderByKey()
                 .endAt(ts)
                 .limitToLast(1)
                 .once('value', function (snapshot) {
                   snap = snapshot.val();
                   console.log('snap: ' + JSON.stringify(snap));

                   key = Object.keys(snap)[0];
                   node = snap[key];
                   console.log('node: ' + JSON.stringify(node));

                   return(node);
                 });
});

Here's the output that I see in my function log:

snap: {"1538004276":{"description":"This the the Basic product","price":40}}

node: {"description":"This the the Basic product","price":40}

Unhandled error RangeError: Maximum call stack size exceeded
    at Object (native)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4905:24
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)

Help!

3
You have almost exactly the problem as the dup. Return the actual raw data, not a snapshot object that contains circular references inside it. - Doug Stevenson
Can you point me to the duplicate that you feel answers this question? Explain how snapshot data has circular references in it. What I’m returning is a simple object with two values as noted above. - Randall Sargent
The problems are similar, but I've now spelled out the specific issue in the answer. - Doug Stevenson

3 Answers

14
votes

The problem is similar to the one described here. You're returning a complex object generated by the Firebase API called a DocumentSnapshot. A snapshot it not itself raw JSON data to return to the client, and it contains circular references to other objects. Cloud Functions is stuck trying to serialize all of these objects. Instead, just return the raw JavaScript object of the data at the location of interest by calling val() on the snapshot:

return database
    .ref('/products/' + product)
    .orderByKey()
    .endAt(ts)
    .limitToLast(1)
    .once('value')             // once() returns a promise containing a snapshost
    .then(snapshot => {
        return snapshot.val()  // this is the raw JS object
    })

You generally don't use both the returned promise and the callback in the same call. It's easier to just use the promise.

3
votes

Solution for me was to wrap return in a Promise:

exports.getIssue = functions.https.onCall(data => {
const issueKey = data.issueKey

return new Promise((resolve, reject) => {
    admin
        .database()
        .ref('issue')
        .child(issueKey)
        .once('value', snapIssue => {
            let issue = snapIssue.val()
            if (issue) {
                return resolve(issue)
            } else {
                reject('Issue not found')
            }
        })
        .catch(reject)
})
3
votes

It is worth mentioning that this is not explicitly written in the Firebase doc; that you should ALWAYS stringify objects before an HTTP request. On the other end simply parse it. Removing JSON.stringify() and JSON.parse() will trigger the exact same error.

CLIENT SIDE:

auth.signInWithEmailAndPassword(username, password).then(async (snapShot) => {
    const getToken = await functions.httpsCallable('getToken')(JSON.stringify(snapShot));
    ....
});

SERVER SIDE:

exports.getToken = functions.https.onCall(async (data, context) => {
    console.log(JSON.parse(data));
    ...
});