I'm using mongoose to insert some data into mongodb. The code looks like:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
var article1 = docs[0], article2 = docs[1];
}
});
};
You can see there are a lot of nested callbacks there, so I'm trying to use q to refactor it.
I hope the code will look like:
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.end();
But I don't know how to do it.
Today we have mongoose-q as well. A plugin to mongoose that gives you stuff like execQ and saveQ which return Q promises.
With alternative deferred promise implementation, you may do it as following:
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:
Then you can use it instead of save to chain your promises
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.
Two years later, this question just popped up in my RSS client ...
Things have moved on somewhat since May 2012 and we might choose to solve this one in a different way now. More specifically, the Javascript community has become "reduce-aware" since the decision to include
Array.prototype.reduce
(and other Array methods) in ECMAScript5.Array.prototype.reduce
was always (and still is) available as a polyfill but was little appreciated by many of us at that time. Those who were running ahead of the curve may demur on this point, of course.The problem posed in the question appears to be formulaic, with rules as follows :
conn.collection(table).insert()
build as follows (whereN
corresponds to the object's index in an array):users
,channels
,articles
.user
,channel
,article
(ie the table names without the pluralizing 's').A general pattern from this article by Taoofcode) for making asynchronous call in series is :
With quite light adaptation, this pattern can be made to orchestrate the required sequencing :
Lastly, here's the promise-returning worker function,
insert()
:Thus, you can specify as parameters passed to
cascadeInsert
, the actual table/property names and the number of users to insert.This works nicely because the tables in the question all have regular plurals (user => users, channel => channels). If any of them was irregular (eg stimulus => stimuli, child => children), then we would need to rethink - (and probably implement a lookup hash). In any case, the adaptation would be fairly trivial.
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
.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 illustrateQ.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: