Q:

Nested async query callback in Mongoose

Context
so I have two mongodb model, account and the conversations.
Each account contains a lager of all the conversations, with who and an ID of the conversation.
I'm trying to create a page that resemble facebook messenger (conversation list on the left, chat on the right).

Problem
The code hangs with a Promise <Pending> error inside chat's async. I'm not the best coder and I don't understand what it's expecting me to do. It sounds like it would need to wait for nested queries or something like that?

Code
router.get('/chat/:id', function(req, res) {
  if (req.user == undefined) {
    return res.redirect('/');
  }
  else {
    async.parallel({
      conversations: function (err, users){
        Account.find({'_id': {$ne: req.user._id}}).exec(err, users)
      },
      chat: function (err,talk){
            Account.find({'_id': req.user._id, 'conversations.with': req.params.id}).exec(function(err, talk){
              //if conversation ID doesn't exist, create.
              if(!talk.length){
                console.log(err + " No convo with ParamsId exists: " + req.params.id);
                var newConvo = new Conversation({
                  owners     : [req.user._id, req.params.id],
                  created_at : Date.now()
                });
                newConvo.save(function(err, convo){
                  //push conversation ID to both users
                  async.parallel({
                    modelAFind: function (A){
                      Account.findByIdAndUpdate(req.user._id, {$push: {'conversations': {'with':req.params.id, 'conversation': convo._id}}}, {safe:true, upsert: true, new : true}).exec(A)
                    },
                    modelBFind: function (B){
                      Account.findByIdAndUpdate(req.params.id, {$push: {'conversations': {'with':req.user._id, 'conversation': convo._id}}}, {safe:true, upsert: true, new : true}).exec(B)
                    }
                  }),
                  Conversation.findOne({'owners': {"$all":[req.user._id, req.params.id]}}).populate('owners messages.from').exec(talk);
                });
            }
            else if (err){console.log(err)}
            //else if conversation ID exists
            else {
              // CODE HANGS HERE!!
              Conversation.findOne({'owners': {"$all":[req.user._id, req.params.id]}}).populate('owners messages.from').exec(talk);
            }
          });
      },
    },
    function(err, result,next){
      var ret = result.conversations;
      console.log(result);
      ret.dataB = result.chat;
      res.render('chat', {
        user: req.user,
        users: ret,
        profile : req.params.id,
        title : req.params.id + 'とチャット',
        conversationId: ret.dataB
      });
    });
  }
});

How can I successfully get the data from Conversation.findOne() ?
Thank you!

A:

I believe the problem is here:
You name the callback provided by async.parallel, as talk, then you also name the result of Account.find, talk as well. 
  chat: function (err,talk){
        Account.find({'_id': req.user._id, 'conversations.with': req.params.id}).exec(function(err, talk){

Then I see later you try to pass the callback, named talk:
Conversation.findOne({'owners': {"$all":[req.user._id, req.params.id]}}).populate('owners messages.from').exec(talk);

But since this talk variable is overshadowed by the result of the earlier Account.find.
So async.parallel callback is never called, so it will never be finished.
To fix it I suggest follow the async and node.js module in general conventions, call it something like callback or cb.
So you change this line:
  chat: function (err,cb){

And later pass it to this:
Conversation.findOne({'owners': {"$all":[req.user._id, req.params.id]}}).populate('owners messages.from').exec(cb);

If you are using node 7.5.0 or later, or willing to use something like Babel, then I suggest using the new await/async Javascript feature. I re-wrote your code to it and it would be a lot cleaner, easier to maintain, easier to understand, harder to make mistakes:
router.get('/chat/:id', async function(req, res) {
  if (req.user == undefined) {
    return res.redirect('/');
  }

  // We haven't called await on this promise yet, so it will be running in
  // parallel with the other stuff bellow
  const conversationsPromise = Account.find({'_id': {$ne: req.user._id}}).exec();

  const talk = await Account.find({'_id': req.user._id, 'conversations.with': req.params.id}).exec();

  let conversation;

  if(!talk.length){
    console.log(err + " No convo with ParamsId exists: " + req.params.id);
    var newConvo = new Conversation({
      owners     : [req.user._id, req.params.id],
      created_at : Date.now()
    });

    // Await both promises in parallel
    await Promise.all([
      Account.findByIdAndUpdate(req.user._id, {$push: {'conversations': {'with':req.params.id, 'conversation': convo._id}}}, {safe:true, upsert: true, new : true}).exec(),
      Account.findByIdAndUpdate(req.params.id, {$push: {'conversations': {'with':req.user._id, 'conversation': convo._id}}}, {safe:true, upsert: true, new : true}).exec(),
    ]);

    conversation = await Conversation.findOne({'owners': {"$all":[req.user._id, req.params.id]}}).populate('owners messages.from').exec();
  }
  else if (err){console.log(err)}
  //else if conversation ID exists
  else {
    conversation = await Conversation.findOne({'owners': {"$all":[req.user._id, req.params.id]}}).populate('owners messages.from').exec();
  }

  var ret = await conversationsPromise;

  console.log({
    chat: conversation,
    conversations: ret,
  });
  ret.dataB = conversation;
  res.render('chat', {
    user: req.user,
    users: ret,
    profile : req.params.id,
    title : req.params.id + 'とチャット',
    conversationId: ret.dataB
  });
}); 

