-1

First I am searching the GroupMember model for all Groups the user is a member of. When they're found I get the result.

I want to loop through the result and get every group from the Group model. But how can I do an asynchronous function inside a for / forEach and go to the next iteration only when the asynchronous function is finished?

Because right now the groups array will get the first iteration over and over.

GroupMember.findAll({
    Where : {
      userID : userID
    }
  })
  .then(function(result) {
    var groups = []
    var itemsProcessed = 0;

    result.forEach(function(listItem, index, array) {
      var groupID = listItem.dataValues.groupID;

      Group.find({
        Where : {
          groupID: groupID
        }
      })
      .then(function(group) {

        groups.push(group.dataValues);
        itemsProcessed++;
        if(itemsProcessed === array.length) {

          done(null, groups);
        }
      });
    })

  })
  .catch(function(error) {
    done(error);
  });


EDIT

Group model

module.exports.getMyGroups = function(userID) {

  return GroupMember.findAll({ attributes: ['groupID'], where: { userID: userID } })
      .then(function(result) {
        return Promise.all(result.map(function(listItem) {
            var groupID = listItem.dataValues.groupID;

            return Group.find({
              attributes: ['groupName', 'groupDescriptionShort', 'createdAt'],
              where: { id: groupID }
            })
                .then(function(group) {
                  return group.dataValues;
                });
        }));
      });

}

Group controller calling the model

module.exports.myGroups = function(req, res) {
  var userID = req.body.userID;

  group.findByUserId(userID).then(
    function(groups) {
      respHandler.json(res, 200, { "groups": groups });
    },
    function(error) {
      respHandler.json(res, 400, { "error": error });
    });
}

Router calling the group controller

router.post('/groups', groupCtrl.myGroups);
Soundwave
  • 605
  • 2
  • 9
  • 22
  • You cannot [use `forEach` with promises](http://stackoverflow.com/a/37576787/1048572) – Bergi May 07 '17 at 13:47
  • 1
    Don't use a `done` callback with promises. Return the promise instead! – Bergi May 07 '17 at 13:48
  • @Bergi By returning the promise, do you mean the first example of YoannM ? Because it works and it seems very clean and the right way to do it. – Soundwave May 07 '17 at 14:26
  • Everywhere. In your function, in the functions from the answer. You're not utilising the true power of promises at all. – Bergi May 07 '17 at 14:27
  • Could you show me an example of how you would do this? – Soundwave May 07 '17 at 14:28
  • @Bergi please look at my edited post, how would you make this cleaner with promises? – Soundwave May 07 '17 at 14:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143606/discussion-between-soundwave-and-bergi). – Soundwave May 07 '17 at 14:31
  • 1
    Drop everything that involves `done`, prepend the `findUserByID` body with a `return`, and then call it as `group.findByUserId(userID, function(groups) { respHandler.json(res, 200, { "groups": groups }); }, function(error) { respHandler.json(res, 400, { "error": error }); });` – Bergi May 07 '17 at 14:32
  • @Bergi Thanks, I think you forgot to add a .then. I edited my code with your way. – Soundwave May 07 '17 at 14:48
  • @Bergi by the way, if an error occurs and the model returns an error. How does the controller know it has to deal with the error function. I see that it takes an error argument but I could as well call it 'foo' or 'bar' right so that doesn't mean anything? (I don't know how to fake an error so not sure how to test this) – Soundwave May 07 '17 at 14:51
  • 1
    You're right, I forgot to replace the comma with `).then(`. Regarding the controller, it *always* needs to handle errors (promise rejections) at the end of the chain. – Bergi May 07 '17 at 15:50
  • @Bergi Right, but if I just 'return error;' how does it know it is a rejection? Or is it because I am sending it from a 'catch' object? But even then it goes down the scope and in the end either the success or error will be a normal return from the GroupMember.findAll function? – Soundwave May 07 '17 at 18:13
  • 1
    No, you shouldn't `return error` at all. Drop the entire `catch` invocation and [the pointless `then`](http://stackoverflow.com/q/41089122/1048572) so that you directly return the promise with its fulfillment or rejection. – Bergi May 07 '17 at 18:20
  • 1
    @Bergi I think I understand you, I updated my model in the post. – Soundwave May 07 '17 at 18:58
  • 1
    Yes, that is exactly how it should look like – Bergi May 07 '17 at 19:09

1 Answers1

1

You can use Promise.all to have a better handling of multiple promise-like executions.

  GroupMember.findAll({
    Where : {
      userID : userID
    }
  })
  .then(function(result) {
     return Promise.all(result.map(function (listItem) {
        var groupId = listItem.dataValues.groupID;

        return Group.find({ Where: { groupId: groupId })
                    .then(function (group) {
                         return group.dataValues;
                    });
     }));
   })
   .then(function (groups) {
      done(null, groups);
   })
   .catch(function(error) {
      done(error);
   });

But if you really need to wait for each iteration before going to the next one, I'd use some other function like that

GroupMember.findAll({
    Where: { userId: userId }
}).then(function (result) {
    var array = [];

    function next () {
        var groupId = result[array.length].dataValues.groupId;

        Group.find({ Where: { groupId: groupId })
             .then(function (group) {
                 array.push(group.dataValues);

                 if (array.length >= result.length) {
                     done(null, array);
                 } else {
                     next();
                 }
             })
             .catch(function (error) {
                 done(error);
             });
    }(); // Directly executed
})
.catch(function(error) {
   done(error);
});
Yoann
  • 3,020
  • 1
  • 24
  • 34