3

Is it possible to process a db.model.find() query inside of function context and retrieve a result without using callbacks and promises with mongoose library?

I need to get assured, if some user exists in process of running controller, so, I can't minimize current scope to callback due to large amount of same operations (for example, communication with database). Also I'm trying to realize MVC model in my project, so, I want to keep the helper libs (modules) in separated files. That's why I don't want to use any callbacks or promises - they will much times complicate everything even more then things already do.


For example, how should I rewrite the following code to be executed successfully (if it's actually possible) (you can ignore login model and controller - they are written to represent complicacy if to rewrite that code using callbacks):

user.js lib

var db = require('./lib/db');

class User{
    constructor(id){ //get user by id
        var result = db.models.user.findOne({_id: id}); //unsupported syntax in real :(
        if(!result || result._id != _id)
            return false;
        else{
            this.userInfo = result;

            return result;
        }

    }
}

module.exports = User;

login model

var user = require('./lib/user')
var model = {};

model.checkUserLogged(function(req){
    if(!req.user.id || req.user.id == undefined)
        return false;

    if(!(this.user = new user(req.user.id)))
        return false;
    else
        return true;
});

module.exports = model;

login controller

var proxy = require('express').router();

proxy.all('/login', function(req, res){

    var model = require('./models/login');

    if(!model.checkUserLogged()){
        console.log('User is not logged in!');
        res.render('unlogged', model);
    }else{
        console.log('User exists in database!');
        res.render('logged_in', model);
    }

});

Generator functions/yields, async/await (es2017), and everything et cetera can be used just to solve the problem without nesting.

Thx in advance.

impulsgraw
  • 887
  • 3
  • 13
  • 34

2 Answers2

4

There are two points wrong:

  • Mongoose methods can't be called synchronously (Anyway a call to a DB done synchronously is not a good idea at all).
  • Nor async/await nor generators can be used in the constructor of an ES6 Class. It is explained in this answer.

If you don't want nested code an easy option could be to use async/await (currently available in Node.js using a flag, not recommended for production). Since Mongoose methods return promises they can be used with async/await.

But as I said you can not do that in the constructor, so it has to be somewhere else.

As an example you could do something like this:

var proxy = require('express').router();
var db = require('./lib/db');

proxy.all('/login', async function(req, res){
    const result = await db.models.user.findOne({_id: req.user.id}).exec();
    if (!result) {
        console.log('User is not logged in!');
        return res.render('unlogged');
    }
    res.render('logged_in');
});
Community
  • 1
  • 1
Antonio Val
  • 3,200
  • 1
  • 14
  • 27
  • I can use async/await (I have the latest node.js by the moment, and I can turn `--harmony` flag on). Ok, let's admit, that that code is located not in constructor, but in some other place of class (for ex., in static function). Could you, please, give me an example of how would it be rewritten using async/await (ES7)? – impulsgraw Jan 16 '17 at 13:18
  • if to `console.log` after `const result =...` it always returns `Promise { }`. How can I get a response object instead of that and *why is it still (by the moment await is finished) pending*? – impulsgraw Jan 16 '17 at 14:14
  • `.exec()` returns a Promise, but using `await` the code execution will not continue until the Promise is resolved. Are you using async/await correctly? – Antonio Val Jan 16 '17 at 14:26
  • I suppose, yes. I have transfigured your example, defining a method like that inside of class: `async getUserIdByEmailPassword(_email, _password){ if(!_email || !_password) return false; var db = this; const res = await db.models.user.findOne({email: _email, password: md5(_password)}).exec(); return res; }` and it just returning **Promise { }** like Promise object is not resolved yet (!?) :c – impulsgraw Jan 16 '17 at 14:59
  • I see, you created a new method doing the call to the DB there, so you have to wait for that one as well, since it returns a Promise. – Antonio Val Jan 16 '17 at 15:14
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/133280/discussion-between-impulsgraw-and-antonio-val). – impulsgraw Jan 16 '17 at 15:28
  • 1
    "Anyway a call to a DB done synchronously is not a good idea at all" Why? In many case you need data from DB and can't continue the program logic, without this data. On sync DB request, you simply do nothing - only wait until data come from database. In async case you MUST in every case handle this nowaiting behaviour - a lot of useless code. On every DB access you have not needed code only enforced from the async moogose design!!! – Gerd Sep 15 '17 at 14:34
  • I said that because Node.js is single threaded, so blocking the thread waiting for an IO operation is not a good idea in general. In case of a server, even if the logic can't continue until you get the data, you need the thread free in order to accept other requests. For scripts and so on maybe blocking the thread waiting for a response could make sense. – Antonio Val Sep 15 '17 at 22:16
0

Old question, but I want to share a method for handling this that I didn't see in my first couple searches.

I want to get data from a model, run some logic and return the results from that logic. I need a promise wrapper around my call to the model.

Below is a slightly abstracted function that takes a model to run a mongoose/mongo query on, and a couple params to help it do some logic. It then returns the value that is expected in the promise or rejects.

export function promiseFunction(aField: string, aValue, model: Model<ADocument, {}>): Promise<aType> {
    return new Promise<string>((resolve, reject) => {
      model.findOne({[aField]: aValue}, (err, theDocument) => {
        if(err){
          reject(err.toString());
        } else {
          if(theDocument.someCheck === true){
            return(theDocument.matchingTypeField)
          } else {
            reject("there was an error of some type")
          }
        }
      });
    })    
}

giveemheller
  • 106
  • 8