async waterfall callback isn't called within mongoose save - node.js

I'm using express, mongoose, and async.
Within an update method on a controller, I'm calling the following:
//note: we're within a constructor's prototype method, hence the 'self'. also body is simply the request body.
async.waterfall([
function(callback) {
self.collection.findById(body._id).exec(function(err, item) {
if(item) {
callback(null, item);
} else {
callback(err);
}
});
},
function(item, callback) {
//callback(null, item); //makes this task work, but not what I need
//merge two together models together, then save
item.save( _.extend(item, body), function(err, item) {
console.log('code here never seems to get called within the waterfall');
callback(null, item);
});
}
], function(err, results) {
console.log('hello', err, results);
self.res.json(results);
});
So basically what I'm trying to do is find a document by id, then merge the new object in with the one I just found, save it, and return the result as JSON. But the callback function nested within the .save never seems to get called. So the entire request seems to hang and the final function in the waterfall never gets called.
I might be wrong, but it seems that the save method's callback gets called asynchronously. How would I go about getting this to work?
Side note: If the save method's callback is async, then why does this seem to work?
var _this = this;
var item = new this.collection(this.req.body);
item.save(function(err, data) {
_this.res.json(data);
});
That method returns the saved object as JSON just fine.

You are not using the Model.save method correctly. It just takes a callback as the first argument. You must fetch your item instance, then set your new property values on your item instance, then do item.save(callback);.

Related

Recover an object requested with MongoDB Driver on node JS

I try to recover object from a mongo DB database, in a node JS file and it doesn't work.
In a file called db.js , i have made the following code :
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinColADSL: function() {
return MongoClient.connect("mongodb://localhost/sdb").then(function(db) {
var collection = db.collection('scollection');
return collection.find({"type" : "ADSL"}).toArray();
}).then(function(items) {
return items;
});
}
};
And, I try to use it in the file server.js :
var db = require(__dirname+'/model/db.js');
var collection = db.FindinColADSL().then(function(items) {
return items;
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
console.log(collection);
In the result I have "Promise { }". Why ?
I just want to obtain an object from the database in order to manipulate it in the others functions situated in the server.js file.
Then then function called on promises returns a promise. If a value is returned within a promise, the object the promise evaluates to is another promise which resolves to the value returned. Take a look at this question for a full explanation of how it works.
If you want to verify that your code is successfully getting the items, you will have to restructure your code to account for the structure of promises.
var db = require(__dirname+'/model/db.js');
var collection = db.FindinColADSL().then(function(items) {
console.log(items);
return items;
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
That should log your items after they are retrieved from the database.
Promises work this way to make working asynchronously more simple. If you put more code below your collection code, it would run at the same time as your database code. If you have other functions within your server.js file, you should be able to call them from within the body of your promises.
As a rule, remember a promise will always return a promise.
The callback functions created in the then() are asynchronous, thus making the console.log command execute before the promise is even resolved. Try placing it inside the callback function instead like below:
var collection = db.FindinColADSL().then(function(items) {
console.log(items)
return items;
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Or, for the sake of another example using the logger functions themselves as the callbacks, and showing that the last console.log call will actually be called before the others.
db.findinColADSL()
.then(console.log)
.catch(console.error)
console.log('This function is triggered FIRST')

NodeJS Express Async issue

I have this function which gets some data from my database but i'm having a trouble calling the function and getting the proper response
function getEvents()
{
var x = [];
var l = dbCollection['e'].find({}).forEach(function(y) {
x.push(y);
});
return x;
});
and another function which calls this function but it always returns undefined.
How can i make the function wait till mongoose has finished filling up the array?
Thanks for the help! My life
dbCollection['e'].find is called non-blocking way so you are returning x before filling. You need to use callbacks or some mongoose promises. You can get all returning values from database like following snippet
function getEvents(callback) {
dbCollection['e'].find({}, function(error, results) {
// results is array.
// if you need to filter results you can do it here
return callback(error, results);
})
}
Whenever you need to call getEvents function you need to pass a callback to it.
getEvents(function(error, results) {
console.log(results); // you have results here
})
You should read mongoose docs for how queries work.
There is also support for promises in mongoose. You can check this url for more information about promises.
The solution proposed by #orhankutlu should work fine.
I will give another solution using promise. You can choose one between these two solutions depending on your style of programming.
Solution using promise:
function getEvents() {
return new Promise(function(resolve, reject){
dbCollection['e'].find({}, function(error, results) {
if (error) return reject(error);
var x = [];
results.forEach(function(y){
x.push(y);
});
// forEach() is a blocking call,
// so the promise will be resolved only
// after the forEach completes
return resolve(x);
});
});
};
Calling getEvents():
getEvents().then(function(result){
console.log(result); //should print 'x'
}).catch(function(err){
// Handle error here in case the promise is rejected
});
I will encourage you to try both the approaches, ie, using callbacks and using promises. Hope you find it useful!

How to save the Object returned from the query.exec() function in mongoose

I am new to mongoose.
Here is my scenario:
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
children: [childSchema]});
var Parent = mongoose.model('Parent', parentSchema);
Say I have created a parent 'p' with children, and I am querying for 'p', using
var query = Parent.find({"_id":"562676a04787a98217d1c81e"});
query.select('children');
query.exec(function(err,person){
if(err){
return console.error(err);
} else {
console.log(person);
}
});
I need to access the person object outside the async function. Any idea on how to do this?
Mongoose's find() method is asynchronous which means you should use a callback that you can wrap the query from the find() method. For example, in your case, you can define a callback as
function getChildrenQuery(parentId, callback){
Parent.find({"_id": parentId}, "children", function(err, docs){
if (err) {
callback(err, null);
} else {
callback(null, docs);
}
});
}
which you can then call like this:
var id = "562676a04787a98217d1c81e";
getChildrenQuery(id, function(err, children) {
if (err) console.log(err);
// do something with children
children.forEach(function(child){
console.log(child.name);
});
});
Another approach you may take is that of promises where the exec() method returns a Promise, so you can do the following:
function getChildrenPromise(parentId){
var promise = Parent.find({_id: parentId}).select("children").exec();
return promise;
}
Then, when you would like to get the data, you should make it async:
var promise = getChildrenPromise("562676a04787a98217d1c81e");
promise.then(function(children){
children.forEach(function(child){
console.log(child.name);
});
}).error(function(error){
console.log(error);
});
you cannot access it outside of the callback (="the async function" you mentioned). That's how node.js works:
your call to the database will take some time - a very long time when you compare it to just execute a simple code statement.
Node.js is non-blocking, so it will not wait for the database to return the result, and it wlll continue immediately by executing the code after your query.exec statement.
so the code you write after the query.exec statement is run BEFORE the database returns the result, it is therefore impossible to use that result there.
BUT... embrace async programming:
just write all the code you need into the "async function"
pass a callback into your function, call it from "the async function" and pass it the query result

Loop data with Node.js

I am having my code as
function updateData(data){
data.forEach(function(obj){
users.find({_id:obj.userId}).toArray(
function(e, res) {
obj.userData = res;
console.log(res)
});
});
return data;
}
The problem is I am unable to find the updated data, I am trying to update my data and adding one more field to it based on userId. The data parameter is an array containing the output from comments table. hope you understand the scenario.
It looks that users.find({...}).toArray(function(...){...}) is going to be asynchronous, so there is no way that you can be sure that the db call has been completed and that each data.obj has been updated before data is returned.
Instead of using the javascript Array.prototype.forEach() function, you could use the NodeJS async.each function from the async library by Caolan which would iterate through the array, update each object and then return the data object only when all functions calls have completed.
For example:
var async = require("async");
function updateData(data){
async.each(data, function(obj, callback) {
users.find({_id:obj.userId}).toArray(
function(e, res) {
obj.userData = res;
callback(e);
}
);
},
function(error){
return data;
}
}

Testing asynchronous middleware functionality with Mongoose

I'm using a save middleware in Mongoose to create a log of activity in the DB whenever some action is taken. Something like
UserSchema.post("save", function (doc) {
mongoose.model("Activity").create({activity: "User created: " + doc._id});
});
This appears to work fine, but the problem is that I can't test it because there is no way to pass a callback to post (which probably would not make sense). I test this out using mocha with:
User.create({name: "foo"}, function (err, user) {
Activity.find().exec(function (err, act) {
act[0].activity.should.match(new RegExp(user._id));
done();
});
});
The problem is that the Activity.create apparently does not finish before .find is called. I can get around this by wrapping .find in setTimeout, but this seems hacky to me. Is there any way to test asynchronous mongoose middleware operations?
Unfortunately, there's not a way to reliably interleave these two asynchronous functions in the way you'd like (as there aren't threads, you can't "pause" execution). They can complete in an inconsistent order, which leaves you to solutions like a timeout.
I'd suggest you wire up an event handler to the Activity class so that when an Activity is written/fails, it looks at a list of queued (hashed?) Activities that should be logged. So, when an activity is created, add to list ("onactivitycreated"). Then, it will eventually be written ("onactivitywritten"), compare and remove successes maybe (not sure what makes sense with mocha). When your tests are complete you could see if the list is empty.
You can use util.inherits(Activity, EventEmitter) for example to extend the Activity class with event functionality.
Now, you'll still need to wait/timeout on the list, if there were failures that weren't handled through events, you'd need to handle that too.
Edit -- Ignore the suggestion below as an interesting demo of async that won't work for you. :)
If you'd like to test them, I'd have a look at a library like async where you can execute your code in a series (or waterfall in this case) so that you can first create a User, and then, once it completes, verify that the correct Activity has been recorded. I've used waterfall here so that values can be passed from one task to the next.
async.waterfall([
function(done) {
User.create({name: "foo"}, function (err, user) {
if (err) { done(err); return; }
done(null, user._id); // 2nd param sent to next task as 1st param
});
},
function(id, done) { // got the _id from above
// substitute efficient method for finding
// the corresponding activity document (maybe it's another field?)
Activity.findById(id, function (err, act) {
if (err) { done(err); return; }
if (act) { done(null, true);
done(null, false); // not found?!
});
}
], function(err, result) {
console.log("Success? " + result);
});
Async post-save middleware will apparently be available in Mongoose 4.0.0:
https://github.com/LearnBoost/mongoose/issues/787
https://github.com/LearnBoost/mongoose/issues/2124
For now, you can work around this by monkey-patching the save method on the document so that it supports async post-save middleware. The following code is working for me in a similar scenario to yours.
// put any functions here that you would like to run post-save
var postSave = [
function(next) {
console.log(this._id);
return next();
}
];
var Model = require('mongoose/lib/model');
// monkey patch the save method
FooSchema.methods.save = function(done) {
return Model.prototype.save.call(this, function(err, result) {
if (err) return done(err, result);
// bind the postSave functions to the saved model
var fns = postSave.map(function(f) {return f.bind(result);});
return async.series(fns,
function(err) {done(err, result);}
);
});
};

Resources