Search code examples
mongodbmongoosecallbacklodashbind

Mongoose error: lodash bind no longer accepts a callback function


The upgrade to MongoDB 7 dropped function callbacks, as has been discussed on other threads. Most times it is fairly easy to replace the callback by .then asyn/await etc, as documented at MongooseError: Model.findOne() no longer accepts a callback at Function. But this one baffled me:

return _.bind(function(req, res, next) {
  var query = this.getQuery(req);
  async.waterfall([
    _.bind(query.exec, query),
  ], this.sendData(req, res, next));
}, this);

Wrapping the lodash bind function in async.waterfall has the effect of a promise: this.sendData is only executed after bind function is complete. However, the "_.bind(query.exec, query)" expression raises the "no longer accepts a callback function" exception. So how do I rewrite this to avoid the exception?

Moving on: I can get the data from the database with this call:

query.exec() .then(result => {
     console.log(result);
 })

So I could do without the async wrapper and do something like:

return _.bind(function(req, res, next) {
    var query = this.getQuery(req);
    query.exec() .then(result => {
       this.sendData(req, res, next);  
   });
})

Currently this does not work as the result of the query needs to be bound to the this object. I can't figure out how to do that.

Update, this is the sendData function which receives the data:

sendData: function(req, res, next) {
   var fields = _parseJSON(req.query.fields, []), optFields;
   if (!_.isArray(fields)) {
      fields = [fields];
   }
  function _sendData(err, data) {
    if (err) {
      res.json({'data':data,'error':err}); 
    } else {
      res.json(data);
    }
  }
  fields = _.intersection(fields, this.model.optionalFields);
  return function(err, data) {
    if (fields.length > 0) {
     var isArray = _.isArray(data);
     async.map(isArray ? data : [data], function(doc, callback) {
      var obj = doc.toObject();
      async.each(fields, function(field, cb) {
        doc['get' + field].call(doc, function(err, value) {
          obj[field] = value;
          cb(err);
        });
      }, function(err) {
        callback(err, obj);
      });
    }, function(err, objs) {
      return _sendData(err, isArray ? objs : objs[0]);
    });
  } else {
    _sendData(err, data);
  }
};

}

It isn't clear to me how the data correctly retrieved by query.exec is passed through sendData back to the calling function. The context of this call is that it returns an array of json objects, such as [{name:"CTP"}, {name:"TES"}], which is then used in a node web page to populate a menu. So sendData needs to extract this data from the result, manipulate if required, and return it.


Solution

  • In your suggested solution using promises, it looks like the issue is caused because you are missing this this in the _.bind function.

    The function should look like this:

    return _.bind(function(req, res, next) {
        var query = this.getQuery(req);
        const callback = this.sendData(req, res, next);
        query.exec().then(result => {
            callback(null, result);
        }).catch(err => callback(err));
    }, this)
    

    You could also replace the call to _.bind entirely by using an arrow function for the outer wrapper, but maybe there is a reason you are using this - e.g.

    return (req, res, next) => {
      const callback = this.sendData(req, res, next);
      this.getQuery(req).exec().then(result => {
        callback(null, result);
      }).catch(err => callback(err));
    }