Search code examples
javascriptnode.jsexpressmongooseforum

JS - Express - Mongoose execute all mongoose's promises before send a response


Im doing a forum api, where a forum has many threads, thread has many posts and post could have many post.

The relations are done like this:

var PostSchema = new Schema({
  text: String,
  authorId: String,
  slug: Number,
  posts: [{ type: Schema.Types.ObjectId, ref: 'Post'}],
  created: { type: Date, default: Date.now }
});

The parent model has a list of ids of son model.

I made my controller like this:

var util = require('util'),
  mongoose = require('mongoose'),
  Forum = mongoose.model('Forum'),
  Thread = mongoose.model('Thread'),
  Post = mongoose.model('Post'),
  async = require('async');

exports.show = function(req, res){
  var forums;

  var getThreads = function(forum) {
    return forum.populate('threads', function(err, _forum){
      if(err) throw new Error(err);
      forum.threads = _forum.threads;
      forum.threads.forEach(getPosts);
      return callback(err);
    }); 
  };

  var getPosts = function(thread) {
    return thread.populate('posts', function(err, _thread){
      if(err) throw new Error(err);
      thread.posts = _thread.posts;
      thread.posts.forEach(getComments);
      return callback(err);
    });
  };

  var getComments = function(post) {
    return post.populate('posts', function(err, _post){
      if(err) throw new Error(err);
      post.posts = _post.posts;
      post.posts.forEach(getComments);
      return callback(err);
    });
  };

  async.parallel([
    function(callback) {
      return Forum.find({ ownerId: req.params.owner_id }).exec(function(err, _forums) {
        if(err) throw new Error(err);
        forums = _forums;
        forums.forEach(getThreads);
        return callback(err);
      });
    }
  ], function(err){
      res.json(forums);
    }
  );

};

I need to made the complete forum object, then use this on the response, as posts has posts i cannot just do a nested populate.

I tried to use the async lib, but it execute the callback function before the promises.

How can i build the complete forum object?


Solution

  • You need to properly handle your tree structure in an asynchronous way. Try this approach:

    (I haven't tested, but hope it works)

    // ...
    var Forum = mongoose.model('Forum');
    
    exports.show = function(req, res){
      //Get the owner's forums
      Forum.find({ ownerId: req.params.owner_id }).exec(function(err, forums) {
        if(err) throw new Error(err);
        if(!forums.length) return response.json(forums); //Send an empty array if no forums where found
    
        //Build forums one by one
        var forum = forums.shift();
        buildForum(forum, function () {
          forum = forums.shift();
          if (forum) {
            buildForum(forum, this);
          } else {
            //All forums were built.
            res.json(forums);
          };
        });
      });
    
      var buildForum = function (forum, onSuccess) {
        forum.populate('threads', function(err, forum){
          if(err) throw new Error(err);
          if(!forum.threads.length) return onSuccess();
    
          //Build threads one by one
          var threads = forum.threads;
          var thread = threads.shift();
          buildThread(thread, function () {
            thread = threads.shift();
            if (thread) {
              buildThread(thread, this);
            } else {
              //All threads were built.
              onSuccess();
            };
          });
        });
      };
    
      var buildThread = function (thread, onSuccess) {
        thread.populate('posts', function(err, thread){
          if(err) throw new Error(err);
          if(!thread.posts.length) return onSuccess();
    
          //Build posts one by one
          var posts = thread.posts;
          var post = posts.shift();
          buildPost(post, function () {
            post = posts.shift();
            if (post) {
              buildPost(post, this);
            } else {
              //All posts were built.
              onSuccess();
            };
          });
        });
      };
    
      var buildPost = function (post, onSuccess) {
        post.populate('posts', function(err, post){
          if(err) throw new Error(err);
          if(!post.posts.length) return onSuccess();
    
          //Build comments one by one
          var posts = post.posts;
          var _post = posts.shift();
          buildPost(_post, function () {
            _post = posts.shift();
            if (_post) {
              buildPost(_post, this);
            } else {
              //All comments were built.
              onSuccess();
            };
          });
        });
      };
    };