node.js chain multiple promises (with mongoose)

11,497

Solution 1

You can do two things:

  • Unnest then callbacks
  • Modularize. These "do product stuff" and "do inventory stuff" things might become their own functions (or even the same?).

In this case, unnesting could do the following (assuming you don't need closures in your commented sections):

function _delete(t, id) { 
    return Promise.cast(Event.find({where: {id: id}}, {transaction: t}))
    .then(function(d){
        if (d) {
            return Promise.cast(d.updateAttributes({status: -1}, {transaction: t}));
        else
            throw new Error('this transaction list does not exist');
    })
    .then(function(){
        // do inventory stuff 
        return Promise.cast(Inventory.update({}).exec())
    })
    .then(function(d){
        // do something 
    })
    .then(function(){
        // do product stuff
        return Promise.cast(Product.update({}).exec())
    })
    .then(function(d){
        // do something 
    });
}

Solution 2

In my projects I use Async.js

I think you need to decompose your _process method into small actions

  1. Actions which depend on result from previous actions - async waterfall pattern might be used here
  2. Actions which don't depend on the previous actions result, they may be executed in parallel
  3. Use some custom process

Here is an example from my app:

async.waterfall([

    function findUser(next) {
        Users.findById(userId, function (err, user){
            if(err) {
                next(new Error(util.format('User [%s] was not found.', userId)));
                return;
            }

            next(null, user);
        });
    },

    function findUserStoriesAndSurveys(user, next) {

        async.parallel([
            function findStories(callback) {
                // find all user stories

                Stories.find({ UserGroups: { $in : user.Groups } })
                    .populate('Topic')
                    .populate('Episodes')
                    .exec(function(err, stories) {
                        if(err) {
                            callback(err);
                            return;
                        }

                        callback(null, stories);
                    });
            },
            function findSurveys(callback) {
                // find all completed surveys

                Surveys.find({
                    User: user
                }).exec(function(err, surveys) {
                    if(err) {
                        callback(err);
                        return;
                    }

                    callback(null, surveys);
                });
            }
        ],
        function(err, results) {
            if(err) {
                next(err);
                return;
            }

            next(null, results[0], results[1]);
        });
    },

    function calculateResult(stories, surveys, next) {

        // do sth with stories and surveys

        next(null, { /* result object */ });
    }

], function (err, resultObject) {
    if (err) {
        res.render('error_template', {
            status: 500,
            message: 'Oops! Server error! Please reload the page.'
        });
    }

    res.send(/* .... */);
});

Please refer to Async docs for a custom process, it really does contain a lot of common patterns, I also use this library in my client JavaScript.

Share:
11,497
Shih-Min Lee
Author by

Shih-Min Lee

I help people get trading ideas in the stock market. https://www.tradeideashq.com/

Updated on August 17, 2022

Comments

  • Shih-Min Lee
    Shih-Min Lee over 1 year

    The following is a typical promise function that I am dealing with.

    var _delete = function(t, id) { 
      return Promise.cast(Event.find({where: {id: id}}, {transaction: t}))
      .then(function(d){
        if (d) {
          // ------- (*)
          return Promise.cast(d.updateAttributes({status: -1}, {transaction: t}))
          .then(function(){
              // do inventory stuff 
              return Promise.cast(Inventory.update({}).exec())
              .then(function(d){
                   // do something 
              })
          }).then(function(){
              // do product stuff
              return Promise.cast(Product.update({}).exec())
              .then(function(d){
                   // do something 
              })
          })
        } else {
          return Promise.reject('this transaction list does not exist');
        }
      });
    };
    

    This looks ok until when I am dealing with more complicated update / creates the code will become really messy.

    Currently what I am doing with promise is that 1. I have a lot of useless return true statements and the only purpose is to go to next .then statement 2. promise are programmed in a nested style. also the input arguments are usually complicated and has more then 1 arguments so that I cannot do something like this

    .then(fun1).then(fun2)

    ... etc

    which makes me unable to 'tap' the .then statement to enable/disable a functionality.

    So my questions is how do I do this correctly? Thanks..


    the following is the really ugly things that I am talking about....

    var _process = function(t, tid) {
      var that = this;
      return Promise.cast(Usermain.find({where: {transaction_id: tid}}))
      .bind({})  // --- (*)
      .then(function(d){
        this.tmain = d;
        return true;   // ---- do nothing, just go to next thennable (is this correct)
      }).then(function(){
        return Promise.cast(Userlist.findAndCountAll({where: {transaction_id: tid}}))
      }).then(function(d){
        this.tlist = d;
        return true;  // ---- do nothing, just go to next thennable (is this correct)
      }).then(function(){
        if (this.tmain.is_processed) {
          return Promise.reject('something is wrong');
        }
        if (this.tlist.count !== this.tmain.num_of_tran) {
          return Promise.reject('wrong');
        }
        return Promise.resolve(JSON.parse(JSON.stringify(this.tlist.rows)))
        .map(function(d){
          if (d.is_processed) return Promise.reject('something is wrong with tran list');
          return true;  // goto next then
        });
      }).then(function(){
        return Promise.cast(this.tmain.updateAttributes({is_processed: 1}, {transaction: t}));
      }).then(function(){
        return Promise.resolve(this.tlist.rows)
        .map(function(d){
          var tranlist = JSON.parse(JSON.stringify(d));
          return Promise.cast(d.updateAttributes({is_processed: 1, date_processed: Date.now()}, {transaction: t}))
          .then(function(d){
            if (!d) {
              return Promise.reject('cannot update tran main somehow');
            } else {
                if (tranlist.amount < 0) {
                  return Usermoney._payBalance(t, tranlist.user_id, -tranlist.amount);
                } else {
                  return Usermoney._receiveBalance(t, tranlist.user_id, tranlist.amount);
                }
              }
          });
        });
      });
    }
    
  • Shih-Min Lee
    Shih-Min Lee over 9 years
    my understanding is promise and async.js can achieve the same thing but more people are switching to promise style programming(correct me if I am wrong). The thing is for nested promises the code still become super messy...
  • Bergi
    Bergi over 9 years
    Please, can you write the OP's specific code with async, instead of only providing some example code?
  • Shih-Min Lee
    Shih-Min Lee over 9 years
    sorry my scenario is alittle complicated. I'll explain more details above.
  • sbedulin
    sbedulin over 9 years
    @ChandlerLee I updated the answer with a snippet from my project, hope this helps.
  • Shih-Min Lee
    Shih-Min Lee about 8 years
    ya thanks that's how I end up doing before processing all later queries / updates. (and store intermediate results in this)