Search code examples
javascriptmongoosepromisebluebird

mongoose sequential promises


I'm trying to do some dynamic queries sequential but for any reason, the next code doesn't fulfil that desired behaviour.

var createEvent = function (user, notification) {
  var action, query;

  query = { agent: notification.agent, story: notification.story, type: notification.type };
  action = { agent: notification.agent, story: notification.story, type: notification.type, ts: notification.ts };

  return mongoose.model('Event').findOne(query).exec()
    .then(function (response) {
      if (response === null) {
        return mongoose.model('Event').create(action)
          .then(function (response) {
            return mongoose.model('User').findByIdAndUpdate(user, { $push: { notifications: { _id: response._id }}});
          });
      }
      return mongoose.model('User').findByIdAndUpdate(user, { $push: { notifications: { _id: notification._id }}}).exec();
    });

  setTimeout(resolve, 3000);
};

var moveNotifications = function (users) {
  var promises = [];

  users.map(function (user) {
    if (user.notifications.length > 0) {
      user.notifications.map(function (notification) {
        promises.push(createEvent(user._id, notification));
      });
    }
  });

  Promise.each(promises, function (queue_item) {
    return queue_item();
  });
};

Could someone help me?


Solution

  • As you are calling createEvent inside the nested Array#map loops, you are starting all the queries at once - what you want to do is just get an array of id and notification to later pass to createEvent in Promsise.each

    Note: Not sure why you use Array#map, as you never return anything from the map callback - you're basically doing Array#forEach

    var moveNotifications = function(users) {
        var items = [];
        users.forEach(function(user) {
            if (user.notifications.length > 0) {
                user.notifications.forEach(function(notification) {
                    items.push({id: user._id, notification: notification});
                });
            }
        });
        return Promise.each(events, function(item) {
            return createEvent(item._id, item.notification);
        });
    }
    

    Alternatively, using Array#concat to flatten a 2 level array that is returned by using (nested) Array#map correctly you can achieve the same result

    var moveNotifications = function(users) {
        return Promise.each([].concat.apply([], users.map(function(user) {
            return user.notifications.map(function(notification) {
                return {id: user._id, notification: notification};
            });
        })), function(item) {
            return createEvent(item._id, item.notification);
        });
    }
    

    The above is easily made even more concise using the following ES2015 syntax:

    • arrow functions =>
    • spread operator ...
    • shorthand Object property names {a, b, c}
    • Destructuring Assignment - Parameter Context Matching ({a, b, c}) =>

    var moveNotifications = users => 
        Promise.each([].concat(...users.map(user => 
            user.notifications.map(notification => ({id: user._id, notification}))
        )), ({id, notification}) => createEvent(id, notification)
    );
    

    The extreme ES2016 one liner version :p

    var moveNotifications = users => Promise.each([].concat(...users.map(user => user.notifications.map(notification => ({id: user._id, notification})))), ({id, notification}) => createEvent(id, notification));