node.js chain multiple promises (with mongoose)
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
- Actions which depend on result from previous actions - async
waterfall
pattern might be used here - Actions which don't depend on the previous actions result, they may be executed in parallel
- 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.
Shih-Min Lee
I help people get trading ideas in the stock market. https://www.tradeideashq.com/
Updated on August 17, 2022Comments
-
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 over 9 yearsmy 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 over 9 yearsPlease, can you write the OP's specific code with
async
, instead of only providing some example code? -
Shih-Min Lee over 9 yearssorry my scenario is alittle complicated. I'll explain more details above.
-
sbedulin over 9 years@ChandlerLee I updated the answer with a snippet from my project, hope this helps.
-
Shih-Min Lee about 8 yearsya thanks that's how I end up doing before processing all later queries / updates. (and store intermediate results in
this
)