56

Everything I can find for rending a page with mongoose results says to do it like this:

users.find({}, function(err, docs){
    res.render('profile/profile', {
        users:     docs
    });
});

How could I return the results from the query, more like this?

var a_users = users.find({}); //non-working example

So that I could get multiple results to publish on the page?

like:

/* non working example */
var a_users    = users.find({});
var a_articles = articles.find({});

res.render('profile/profile', {
      users:    a_users
    , articles: a_articles
});

Can this be done?

MrBojangles
  • 1,423
  • 3
  • 14
  • 16
  • 1
    Your dream will come true when [ES7 rolls around](http://jakearchibald.com/2014/es7-async-functions/). – royhowie Jun 13 '15 at 04:23
  • 1
    You should check out [deasync](https://www.npmjs.com/package/deasync), so functions look like they are executed synchronously. – Luca Steeb Jul 25 '15 at 21:24
  • for better understanding of this problem and its solution refer to https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call – devprashant Jun 02 '17 at 16:49

6 Answers6

67

You're trying to force a synchronous paradigm. Just does't work. node.js is single threaded, for the most part -- when io is done, the execution context is yielded. Signaling is managed with a callback. What this means is that you either have nested callbacks, named functions, or a flow control library to make things nicer looking.

https://github.com/caolan/async#parallel

async.parallel([
   function(cb){
      users.find({}, cb);
   },
   function(cb){
      articles.find({}, cb);
   }
], function(results){
   // results contains both users and articles
});
Josh
  • 12,602
  • 2
  • 41
  • 47
  • @josh is there a way to access the results of the first function in the second? or are they only available in the callback? – Philipp Kyeck Jul 14 '11 at 14:03
  • 4
    @pkyeck you can if you use waterfall or one of the other methods that do things serially. In this example, both these methods are allowed to run in parallel using process.nextTick, meaning the results from function 1 are not available to function 2 – Josh Jul 19 '11 at 16:47
  • Async NPM seems to be very powerful, sometimes we need some synchronous tasks. – Ito Feb 19 '13 at 21:54
  • 3
    This works and is perfectly good answer (upvoted it! :) ), but these days - I would say using `async` for parallel tasks is a little obsolete. Which is perfectly understandable, considering this solution was written in 2011. **Promises** offer a lot more flexibility when handling asynchronous tasks, and I would recommend anyone to consider using them before resorting to different solutions (**disclaimer:** I posted answer in this thread demonstrating their use in this very case). – bardzusny Aug 28 '15 at 09:01
  • Is there a way to access properties on "results" like results.users or results.articles ? – olefrank Sep 21 '16 at 09:50
  • Never mind, found the answer in the docs! http://caolan.github.io/async/docs.html#.parallel – olefrank Sep 21 '16 at 11:45
19

I'll play the necromancer here, as I still see another, better way to do it.

Using wonderful promise library Bluebird and its promisifyAll() method:

var Promise = require('bluebird');
var mongoose = require('mongoose');

Promise.promisifyAll(mongoose); // key part - promisification

var users, articles; // load mongoose models "users" and "articles" here

Promise.props({
    users: users.find().execAsync(),
    articles: articles.find().execAsync()
  })
  .then(function(results) {
    res.render('profile/profile', results);
  })
  .catch(function(err) {
    res.send(500); // oops - we're even handling errors!
  });

Key parts are as follows:

Promise.promisifyAll(mongoose);

Makes all mongoose (and its models) methods available as functions returning promises, with Async suffix (.exec() becomes .execAsync(), and so on). .promisifyAll() method is nearly-universal in Node.JS world - you can use it on anything providing asynchronous functions taking in callback as their last argument.

Promise.props({
    users: users.find().execAsync(),
    articles: articles.find().execAsync()
  })

.props() bluebird method takes in object with promises as its properties, and returns collective promise that gets resolved when both database queries (here - promises) return their results. Resolved value is our results object in the final function:

  • results.users - users found in the database by mongoose
  • results.articles - articles found in the database by mongoose (d'uh)

As you can see, we are not even getting near to the indentation callback hell. Both database queries are executed in parallel - no need for one of them to wait for the other. Code is short and readable - practically corresponding in length and complexity (or rather lack of it) to wishful "non-working example" posted in the question itself.

Promises are cool. Use them.

bardzusny
  • 3,788
  • 7
  • 30
  • 30
16

The easy way:

var userModel = mongoose.model('users');
var articleModel = mongoose.model('articles');
userModel.find({}, function (err, db_users) {
  if(err) {/*error!!!*/}
  articleModel.find({}, function (err, db_articles) {
    if(err) {/*error!!!*/}
    res.render('profile/profile', {
       users: db_users,
       articles: db_articles
    });
  });
});

Practically every function is asynchronous in Node.js. So is Mongoose's find. And if you want to call it serially you should use something like Slide library.

But in your case I think the easiest way is to nest callbacks (this allows f.e. quering articles for selected previously users) or do it completly parallel with help of async libraries (see Flow control / Async goodies).

Oleg Shparber
  • 2,732
  • 1
  • 18
  • 19
  • I figured out I could do it this way, however didn't fix my bigger underlaying problem. My example probably wasn't the best. I understand keeping things async, but the big problem is that I'm trying to keep the code more organized into a truer MVC fashion and having a difficult time doing that. I hate the idea that in the controller for every page I have to be making all of these queries--that in my mind should be handled in the Model. So I'm trying to figure out a way of keeping the model in one place and requesting the information and returning it to the controller. – MrBojangles May 31 '11 at 02:50
  • @user776796 In my project I placed models in separate files that are loading before controllers. Mongoose's models are inside my models and I added shortcut functions for frequently used queries. – Oleg Shparber May 31 '11 at 08:42
  • That sounds like how I have things setup. How are you returning the query results from the shortcut functions? – MrBojangles May 31 '11 at 14:25
  • 4
    @user776796 I do not return results but pass to callback passed to shortcut function. – Oleg Shparber May 31 '11 at 15:40
1

I have a function that I use quite a bit as a return to Node functions.

function freturn (value, callback){
    if(callback){
        return callback(value); 
    }
    return value; 
}; 

Then I have an optional callback parameter in all of the signatures.

Joe
  • 185
  • 2
  • 4
1

I was dealing with a very similar thing but using socket.io and DB access from a client. My find was throwing the contents of my DB back to the client before the database had a chance to get the data... So for what it's worth I will share my findings here:

My function for retrieving the DB:

//Read Boards - complete DB

var readBoards = function() {
        var callback = function() {
            return function(error, data) {
                if(error) {
                    console.log("Error: " + error);
                }
                console.log("Boards from Server (fct): " + data);

            }
        };

        return boards.find({}, callback());
    };

My socket event listener:

socket.on('getBoards', function() {
        var query = dbConnection.readBoards();
        var promise = query.exec();
        promise.addBack(function (err, boards) {
            if(err)
                console.log("Error: " + err);
            socket.emit('onGetBoards', boards);
        });
    });

So to solve the problem we use the promise that mongoose gives us and then once we have received the data from the DB my socket emits it back to the client...

For what its worth...

Sten Muchow
  • 6,623
  • 4
  • 36
  • 46
1

You achieve the desired result by the following code. Hope this will help you.

var async = require('async');

// custom imports
var User = require('../models/user');
var Article = require('../models/article');

var List1Objects = User.find({});
var List2Objects = Article.find({});
var resourcesStack = {
    usersList: List1Objects.exec.bind(List1Objects),
    articlesList: List2Objects.exec.bind(List2Objects),
};

async.parallel(resourcesStack, function (error, resultSet){
    if (error) {
        res.status(500).send(error);
        return;
    }
    res.render('home', resultSet);
});
williamli
  • 3,846
  • 1
  • 18
  • 30