简体   繁体   中英

node.js chain multiple promises (with mongoose)

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);
            }
          }
      });
    });
  });
}

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 
    });
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM