Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
            }
          }
      });
    });
  });
}
like image 235
Shih-Min Lee Avatar asked Jan 14 '15 07:01

Shih-Min Lee


People also ask

How do you handle multiple promises in node JS?

In this approach, we will use Promise. all() method which takes all promises in a single array as its input. As a result, this method executes all the promises in itself and returns a new single promise in which the values of all the other promises are combined together.

Can Mongoose queries be chained?

The Mongoose Query class provides a chaining interface for finding, updating, and deleting documents.

How many promises can node js handle?

Assuming we have the processing power and that our promises can run in parallel, there is a hard limit of just over 2 million promises.

Can promises be chained?

Example 2: Chaining the Promise with then() Promise resolved You can call multiple functions this way. In the above program, the then() method is used to chain the functions to the promise. The then() method is called when the promise is resolved successfully. You can chain multiple then() methods with the promise.


2 Answers

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 
    });
}
like image 145
Bergi Avatar answered Sep 18 '22 23:09

Bergi


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.

like image 38
sbedulin Avatar answered Sep 18 '22 23:09

sbedulin