【问题标题】:RangeError: Maximum call stack size exceeded on Nodejs + Mongoose built APIRangeError:Nodejs + Mongoose 构建的 API 超过了最大调用堆栈大小
【发布时间】:2017-11-28 17:26:39
【问题描述】:

在使用 mongoose 和 Nodejs 时,我收到了超出最大调用堆栈大小的错误。这是错误

RangeError: Maximum call stack size exceeded
    at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2045:24)
    at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:252:18)
    at cloneArray (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:362:14)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:247:12)
    at cloneObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:343:13)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:260:16)
    at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2092:13)

我相信这意味着我在某处造成了无限循环,但我不确定是什么导致了错误。路线工作正常,然后我将 mongoose-autopopulate 插件添加到应用程序中。正确发送带有令牌的 POST 到我的驱动器发布路线时,我收到此错误。没有任何记录,服务器停止。

这是我在 drive.js 中的路线示例

   router.post('/', function(req, res, next){
  var decoded = jwt.decode(req.query.token);
  User.findById(decoded.user._id, function(err, user){
    if (err) {
      return res.status(500).json({
        title: 'There was a server error',
        error: err
      });
    }
    var drive = new Drive({
      startAddress: req.body.startAddress,
      endAddress: req.body.endAddress,
      tripDate: req.body.tripDate,
      tripHour: req.body.tripHour,
      price: req.body.price,
      numberOfPassengers: req.body.numberOfPassengers,
      user: user
    });
    drive.save(function (err, result) {
      if(err) {
        return res.status(500).json({
          title: 'There was an error saving the drive collection',
          error: err
        });
      }
      user.drives.push(result);
      user.save();
      res.status(201).json({
        message: 'Drive saved',
        obj: result
      });
    });
  });
});

这是相关的模型

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user.model');
var Trip = require('./trip.model');
var autoPopulate = require('mongoose-autopopulate');



var driveSchema = new Schema({
  startAddress: {type: String, required: true, lowercase: true},
  endAddress: {type: String, required: true, lowercase: true},
  tripDate: {type: Date, required: true},
  tripHour: {type: String, required: true},
  price: {type: Number, required: true},
  numberOfPassengers: {type: Number, required: true},
  trip: {type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true},
  user: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true}
});

driveSchema.post('remove', function(drive) {
  User.findById(drive.user, function(err, user) {
    user.drives.pull(drive);
    user.save();
  });
});

driveSchema.plugin(autoPopulate);


module.exports = mongoose.model('Drive', driveSchema);

我所有的模型都遵循相同的方法和查询。有什么特别的我做错了吗?我查了一下,似乎我可以调用一个实例而不是 JSON,这会破坏代码,但我没有足够的经验来确定该实例在哪里,或者是什么导致重复调用如 .find() 或 .where()我正在使用它正在破坏它。

这是我的其他模型

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autopopulate = require('mongoose-autopopulate');

var tripSchema = new Schema({
  tripActivated: {type: Boolean},
  tripCompleted: {type: Boolean},
  driver: {type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true},
  riders: [{type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}],
  comments: [{type: Schema.Types.ObjectId, ref: 'Comment', autopopulate: true}]
});

tripSchema.post('remove', function(trip) {
  User.findById(trip.driver.user, function(err, user) {
    user.trips.pull(trip);
    user.save();
  });
});


// tripSchema.post('remove', function(trip) {
//   User.findById(trip.riders.user, function(err, user) {
//     user.trips.pull(trip);
//     user.save();
//   });
// });

tripSchema.plugin(autopopulate);

module.exports = mongoose.model('Trip', tripSchema);


//// NEW MODEL

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user.model');
var Trip = require('./trip.model');
var autoPopulate = require('mongoose-autopopulate');



var rideSchema = new Schema({
  startAddress: {type: String, required: true, lowercase: true},
  endAddress: {type: String, required: true, lowercase: true},
  tripDate: {type: Date, required: true},
  tripHour: {type: String, required: true},
  numberOfPassengers: {type: Number, required: true},
  trip: {type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true},
  user: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true}
});

rideSchema.post('remove', function(ride) {
  User.findById(ride.user, function(err, user) {
    user.rides.pull(ride);
    user.save();
  });
});

rideSchema.plugin(autoPopulate);


module.exports = mongoose.model('Ride', rideSchema);


////// NEW MODEL

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user.model');
var autoPopulate = require('mongoose-autopopulate');


var requestSchema = new Schema({
  typeOfRequest: {type: String, required: true},
  driver: {type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true},
  rider: {type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}
});

requestSchema.post('remove', function(request) {
  User.findById(request.user, function(err, user) {
    user.requests.pull(request);
    user.save();
  });
});

requestSchema.plugin(autoPopulate);


module.exports = mongoose.model('Request', requestSchema);

//// 新模型

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autoPopulate = require('mongoose-autopopulate');


var User = require('./user.model');

var messageSchema = new Schema ({
  content: {type: String, required: true},
  receiver: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true },
  user: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true}
});

messageSchema.post('remove', function(message) {
  User.findById(message.user, function(err, user) {
    user.messages.pull(message);
    user.save();
  });
});

messageSchema.plugin(autoPopulate);


module.exports = mongoose.model('Message', messageSchema);

深入研究后,我的错误似乎源于 node_modules 中猫鼬源代码中加星标的这段代码

Document.prototype.$toObject = function(options, json) {
  ***var defaultOptions = {
    transform: true,
    json: json,
    retainKeyOrder: this.schema.options.retainKeyOrder,
    flattenDecimals: true
  };***

  // _isNested will only be true if this is not the top level document, we
  // should never depopulate
  if (options && options.depopulate && options._isNested && this.$__.wasPopulated) {
    // populated paths that we set to a document
    return clone(this._id, options);
  }

  // When internally saving this document we always pass options,
  // bypassing the custom schema options.
  if (!(options && utils.getFunctionName(options.constructor) === 'Object') ||
      (options && options._useSchemaOptions)) {
    if (json) {
      options = this.schema.options.toJSON ?
        clone(this.schema.options.toJSON) :
        {};
      options.json = true;
      options._useSchemaOptions = true;
    } else {
      options = this.schema.options.toObject ?
        clone(this.schema.options.toObject) :
        {};
      options.json = false;
      options._useSchemaOptions = true;
    }
  }

  for (var key in defaultOptions) {
    if (options[key] === undefined) {
      options[key] = defaultOptions[key];
    }
  }

  ('minimize' in options) || (options.minimize = this.schema.options.minimize);

  // remember the root transform function
  // to save it from being overwritten by sub-transform functions
  var originalTransform = options.transform;

  options._isNested = true;

  var ret = clone(this._doc, options) || {};

  if (options.getters) {
    applyGetters(this, ret, 'paths', options);
    // applyGetters for paths will add nested empty objects;
    // if minimize is set, we need to remove them.
    if (options.minimize) {
      ret = minimize(ret) || {};
    }
  }

  if (options.virtuals || options.getters && options.virtuals !== false) {
    applyGetters(this, ret, 'virtuals', options);
  }

  if (options.versionKey === false && this.schema.options.versionKey) {
    delete ret[this.schema.options.versionKey];
  }

  var transform = options.transform;

  // In the case where a subdocument has its own transform function, we need to
  // check and see if the parent has a transform (options.transform) and if the
  // child schema has a transform (this.schema.options.toObject) In this case,
  // we need to adjust options.transform to be the child schema's transform and
  // not the parent schema's
  if (transform === true ||
      (this.schema.options.toObject && transform)) {
    var opts = options.json ? this.schema.options.toJSON : this.schema.options.toObject;

    if (opts) {
      transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
    }
  } else {
    options.transform = originalTransform;
  }

  if (typeof transform === 'function') {
    var xformed = transform(this, ret, options);
    if (typeof xformed !== 'undefined') {
      ret = xformed;
    }
  }

  return ret;
};

我设置了 mongoose.set('debugger', true);以获得更好的错误。当尝试在ride.js 上发帖时,应用程序会注册 POST 请求,找到用户 ID,处理新的 Ride(基于模型),将 Ride 插入数据库,然后立即崩溃。

这是猫鼬日志的错误

Mongoose: users.ensureIndex({ email: 1 }, { unique: true, background: true })
Successfully connected to localhost:27017/atlas
Mongoose: users.findOne({ _id: ObjectId("59506e1629cdff044664f21c") }, { fields: {} })
(node:1219) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
Mongoose: rides.insert({ startAddress: 's', endAddress: 's', tripDate: new Date("Fri, 22 Feb 2222 00:00:00 GMT"), tripHour: '8', numberOfPassengers: 2, user: ObjectId("59506e1629cdff044664f21c"), _id: ObjectId("595078cf0b46e704c3091070"), __v: 0 })
events.js:160
      throw er; // Unhandled 'error' event
      ^

RangeError: Maximum call stack size exceeded
    at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2045:24)
    at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:253:18)
    at cloneArray (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:363:14)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:247:12)
    at cloneObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:344:13)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:261:16)
    at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2092:13)
    at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:253:18)
    at cloneObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:344:13)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:261:16)
    at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2092:13)
    at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
    at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:253:18)
    at cloneArray (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:363:14)

**********更新

经过一番挖掘,这是破坏应用程序的代码

user.drives.push(result); <----------
          user.save();

它是将数据插入 mongodb,然后当它尝试推送给用户时,它会中断。知道为什么吗?我添加了我的用户模型以供参考。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var mongooseUniqueValidator = require('mongoose-unique-validator');
var autoPopulate = require('mongoose-autopopulate');


    var userSchema = new Schema({
      email: {type: String, required: true, unique: true, lowercase:true},
      password: {type: String, required: true},
      profileImgUrl: {type: String},
      fName: {type: String},
      lName: {type: String},
      yearOfBirth: {type: String},
      gender: {type: String},
      ratings: [{type: Number}],
      comments: [{type: Schema.Types.ObjectId, ref: 'Comment', autopopulate: true}],
      messages: [{type: Schema.Types.ObjectId, ref: 'Message', autopopulate: true}],
      rides: [{type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}],
      drives: [{type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true}],
      requests: [{type: Schema.Types.ObjectId, ref: 'Request', autopopulate: true}],
      trips: [{type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true}]
    });

    userSchema.plugin(mongooseUniqueValidator);
    userSchema.plugin(autoPopulate);

    module.exports = mongoose.model('User', userSchema);

【问题讨论】:

  • 你是什么意思“递归API调用”?您认为您可以突出显示该特定代码吗?你试过调试器吗?立即困扰我的是我看到定义了两次“删除后”挂钩,我还想知道您是否设法通过在此处未显示的其他模式上执行相同的操作以循环方式执行此操作。请尝试缩小问题首先出现的位置,并查看其他架构,以防你正在做我认为你正在做的事情,
  • 调试器语句应该放在哪里?并且移除第二个 post remove 钩子并没有解决问题。
  • @NeilLunn 我将其缩小到仅此 POST 和模型,仍在努力缩小范围。
  • 好吧,如果您使用调试器并简单地在错误时停止执行,那么它将向您显示错误发生的位置,您可以返回查看实际调用的位置。如果您知道您有一个递归调用,然后在此处设置断点并单步执行。但我也说过,您在这里发布删除挂钩似乎非常可疑,您可能还在受影响的相关模型上定义了它们。这是触发无限循环的可靠方法。但是您没有像要求的那样使用那些模型的代码更新您的问题。
  • @NeilLunn 我添加了所有其他模型。

标签: node.js api mongoose model


【解决方案1】:

问题出在我的用户模型中。我正在使用 mongoose-autopopulate 插件并将自动填充设置为其中包含用户实例的真实对象。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var mongooseUniqueValidator = require('mongoose-unique-validator');
var autoPopulate = require('mongoose-autopopulate');


    var userSchema = new Schema({
      email: {type: String, required: true, unique: true, lowercase:true},
      password: {type: String, required: true},
      profileImgUrl: {type: String},
      fName: {type: String},
      lName: {type: String},
      yearOfBirth: {type: String},
      gender: {type: String},
      ratings: [{type: Number}],
      comments: [{type: Schema.Types.ObjectId, ref: 'Comment', autopopulate: true}],
      messages: [{type: Schema.Types.ObjectId, ref: 'Message', autopopulate: true}],<---------
      rides: [{type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}],<---------
      drives: [{type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true}],<-----------
      requests: [{type: Schema.Types.ObjectId, ref: 'Request', autopopulate: true}],
      trips: [{type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true}]
    });

    userSchema.plugin(mongooseUniqueValidator);
    userSchema.plugin(autoPopulate);

    module.exports = mongoose.model('User', userSchema);

我还在这些模型上将 autopopulate 设置为 true。这在填充给定模型和用户模型之间调用了无限循环。

如果有人遇到这个问题。不要像我一样在堆栈溢出上发布一堆代码。弄清楚你在调用什么来调用它。就我而言,我在两个可以来回通信的模型上调用自动填充。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-27
    • 2014-08-19
    • 2013-08-18
    • 2017-03-26
    • 2012-05-27
    • 2020-04-14
    相关资源
    最近更新 更多