firebase-functions: Receiving exception of stackoverflow by lodash from simple queries on onCall but not onRequest

from package.json:

        "firebase-admin": "~5.12.1",
        "firebase-functions": "^1.0.3",
        "mysql2": "^1.5.3",
        "sequelize": "^4.37.10"

And using this code:

const models = require('./db/models');

const throw_db_issue = e => {
  throw new functions.https.HttpsError('failed-db-issue', `DB issue: ${e.message}`);
};

exports.all_categories = functions.https.onCall((data, context) => {
  return models.Category.findAll({
    where: { parent_id: null },
    include: ['children', models.CategoryImage],
  }).catch(throw_db_issue);
});

Where models is defined as:

const Sequelize = require('sequelize');
const CategoryImage = sequelize.define(
  'category_images',
  {
    category_id: Sequelize.INTEGER,
    image_path: Sequelize.STRING,
  },
  {
    underscored: true,
  }
);

const Category = sequelize.define(
  'categories',
  {
    name: Sequelize.STRING,
    parent_id: Sequelize.INTEGER,
  },
  {
    underscored: true,
  }
);
CategoryImage.belongsTo(Category);
Category.hasOne(CategoryImage);

And I get this stack trace in firebase console:

Unhandled error RangeError: Maximum call stack size exceeded
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:204:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:204:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:204:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:204:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)

But this is fine if I run this code as under plain onRequest.

Please fix/suggest solution as I prefer onCall to onRequest

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

Hi. This error occurs when your callable function returns an object that has cycle within it.

In @diegogarciar’s case, the issue is that the function is returning the result of the call to userGroupsRef.once, which is a DataSnapshot. I suspect that instead of this:

return userGroupsRef.once("value", snapshot =>{

you probably meant to write this:

return userGroupsRef.once("value").then(snapshot =>{

The snapshot is the same either way, but if you want to chain the promises so that everything runs before the function completes (and you do), you have to do the latter.

In @fxfactorial’s case, it looks like it’s returning an array of sequelize Models. I’m assuming that object has some cycle in it. I’m not sure what the best fix there would be. If you could transform each Model into a plain JavaScript object with just the fields you care about, that would fix it. Maybe instead of this:

return models.Category.findAll({
    where: { parent_id: null },
    include: ['children', models.CategoryImage],
  }).catch(throw_db_issue);

you could do this:

return models.Category.findAll({
    where: { parent_id: null },
    include: ['children', models.CategoryImage],
  }).then(models => {
    for (var i = 0; i < models.length; i++) {
      models[i] = models[i].toJSON();
    }
    return models;
  }).catch(throw_db_issue);

Thanks, @Danebrouwer97. That’s an interesting point. We should consider whether we can support toJSON on objects that have them without breaking the custom formatting for certain types.

Thanks for the speedy response,

Just an additional note: I see in the encoding source file, that it uses the following code when dealing with an object.

if (_.isObject(data)) {
    // It's not safe to use _.forEach, because the object might be 'array-like'
    // if it has a key called 'length'. Note that this intentionally overrides
    // any toJSON method that an object may have.
    return _.mapValues(data, encode);
  }

However, using this method ignores any custom toJSON methods that are usually called when ‘stringifying’ the object.

This is the source of the issue in using Sequelize and returning an instance/instances of a model from firebase. An instance of a model has recursion and its custom toJSON method normally removes that, this can be easily tested by returning the ‘stringified’ instance from a firebase function.

return JSON.parse(JSON.stringify( ... )); // works

The easiest solution that I can see is to just update the error with a more intuitive output, perhaps even mention the fact that firebase’s encoding method ignores any custom toJSON method.

Glad to hear 😃 thanks for closing. As always, feel free to open another issue if you encounter any other problems!

yes, thank you!

as @bklimt suggester using .then() as return userGroupsRef.once("value").then(snapshot =>{ }) solved the issue perfectly, I adopted the syntax everywhere else. Thanks by the way @bklimt