How to use "q" module for refactoring mongoose code? How to use "q" module for refactoring mongoose code? mongoose mongoose

How to use "q" module for refactoring mongoose code?


You'll want to use Q.nfcall, documented in the README and the Wiki. All Mongoose methods are Node-style. I'll also use .spread instead of manually destructuring .then.

var mongoose = require('mongoose');mongoose.connect('mongo://localhost/test');var conn = mongoose.connection;var users = conn.collection('users');var channels = conn.collection('channels');var articles = conn.collection('articles');function getInsertedArticles() {    return Q.nfcall(users.insert.bind(users), [{/*user1*/},{/*user2*/}]).spread(function (user1, user2) {        return Q.nfcall(channels.insert.bind(channels), [{userId:user1._id},{userId:user2._id}]).spread(function (channel1, channel2) {            return Q.nfcall(articles.insert.bind(articles), [{userId:user1._id,channelId:channel1._id},{}]);        });    })}getInsertedArticles()    .spread(function (article1, article2) {        // you only get here if all three of the above steps succeeded    })    .fail(function (error) {        // you get here if any of the above three steps failed    });

In practice, you will rarely want to use .spread, since you usually are inserting an array that you don't know the size of. In that case the code can look more like this (here I also illustrate Q.nbind).


To compare with the original one is not quite fair, because your original has no error handling. A corrected Node-style version of the original would be like so:

var mongoose = require('mongoose');mongoose.connect('mongo://localhost/test');var conn = mongoose.connection;function getInsertedArticles(cb) {    // insert users    conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {        if (err) {            cb(err);            return;        }        var user1 = docs[0], user2 = docs[1];        // insert channels        conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {            if (err) {                cb(err);                return;            }            var channel1 = docs[0], channel2 = docs[1];            // insert articles            conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {                if (err) {                    cb(err);                    return;                }                var article1 = docs[0], article2 = docs[1];                cb(null, [article1, article2]);            }        });    };}getInsertedArticles(function (err, articles) {    if (err) {        // you get here if any of the three steps failed.        // `articles` is `undefined`.    } else {        // you get here if all three succeeded.        // `err` is null.    }});


With alternative deferred promise implementation, you may do it as following:

var mongoose = require('mongoose');mongoose.connect('mongo://localhost/test');var conn = mongoose.connection;// Setup 'pinsert', promise version of 'insert' methodvar promisify = require('deferred').promisifymongoose.Collection.prototype.pinsert = promisify(mongoose.Collection.prototype.insert);var user1, user2;// insert usersconn.collection('users').pinsert([{/*user1*/},{/*user2*/}])// insert channels.then(function (users) {  user1 = users[0]; user2 = users[1];  return conn.collection('channels').pinsert([{userId:user1._id},{userId:user2._id}]);})// insert articles.match(function (channel1, channel2) {  return conn.collection('articles').pinsert([{userId:user1._id,channelId:channel1._id},{}]);}).done(function (articles) {  // Do something with articles}, function (err) {   // Handle any error that might have occurred on the way});    


Considering Model.save instead of Collection.insert (quite the same in our case).

You don't need to use Q, you can wrap yourself the save method and return directly a Mongoose Promise.

First create an utility method to wrap the save function, that's not very clean but something like:

  //Utility function (put it in a better place)  var saveInPromise = function (model) {    var promise = new mongoose.Promise();    model.save(function (err, result) {      promise.resolve(err, result);    });    return promise;  }

Then you can use it instead of save to chain your promises

  var User = mongoose.model('User');  var Channel = mongoose.model('Channel');  var Article = mongoose.model('Article');  //Step 1  var user = new User({data: 'value'});  saveInPromise(user).then(function () {    //Step 2    var channel = new Channel({user: user.id})    return saveInPromise(channel);  }).then(function (channel) {    //Step 3    var article = new Article({channel: channel.id})    return saveInPromise(article);  }, function (err) {    //A single place to handle your errors  });

I guess that's the kind of simplicity we are looking for.. right? Of course the utility function can be implemented with better integration with Mongoose.

Let me know what you think about that.


By the way there is an issue about that exact problem in the Mongoose Github:

I hope it's gonna be solved soon. I think it takes some times because they are thinking of switching from mpromise to Q: See here and then here.