mongoose exec call with async.parallel - node.js

I have a code like this in my express controller
function (req, res) {
var queries = [];
data.forEach(function (item) {
var query = myModel.findOneAndUpdate({remoteId: item.id}, item, {upsert: true}).exec;
queries.push(query);
});
async.parallel(queries, function (err, docs) {
res.json(docs);
});
});
If data array has 3 item, then i have an array of 3 null values.
async.parallel function accepts a function with a callback argument, that should be called to properly complete its execution. So mongoose.Query.exec does the same. But i recieve an array of null objects as a result.
If i wrap my exec call like so
var query = function (cb) {
tournamentsModel.findOneAndUpdate({remoteId: item.id}, item, {upsert: true}).exec(function (err, model) {
cb(err, model);
});
};
queries.push(query);
everything is ok and i recieve 3 docs from mongo as a result.
Why should i explicitly call a callback passed to a async.parallel function call, when exec method does the same?

When you directly pass the exec function of your query to async.parallel as a function to execute, you're losing the this context of that function call that contains your query to run.
To use this approach, you would need to call bind to return a new function that will call exec with the right this context; so something like this:
var query = Query.prototype.exec.bind(
myModel.findOneAndUpdate({remoteId: item.id}, item, {upsert: true})
);
queries.push(query);
It's probably cleaner to call exec yourself, but pass in the async callback to it:
var query = function(cb) {
myModel.findOneAndUpdate({remoteId: item.id}, item, {upsert: true}).exec(cb);
}
queries.push(query);

Related

Understanding callback on this MDN example

The code comes from an MDN tutorial on how to use Node.js and mongoose. The idea is to make parallel request to get the count of documents in different models. I don't understand where the callback passed to each async.parallel comes from, where it is defined and what it does, it seems like a dummy function to me. Could you help me understand it? Here is the code:
var Book = require('../models/book');
var Author = require('../models/author');
var Genre = require('../models/genre');
var BookInstance = require('../models/bookinstance');
var async = require('async');
exports.index = function(req, res) {
async.parallel({
book_count: function(callback) {
Book.countDocuments({}, callback); // Pass an empty object as match condition to find all documents of this collection
},
book_instance_count: function(callback) {
BookInstance.countDocuments({}, callback);
},
book_instance_available_count: function(callback) {
BookInstance.countDocuments({status:'Available'}, callback);
},
author_count: function(callback) {
Author.countDocuments({}, callback);
},
genre_count: function(callback) {
Genre.countDocuments({}, callback);
}
}, function(err, results) {
res.render('index', { title: 'Local Library Home', error: err, data: results });
});
};
the callback is passed by the async package.
Explanation:
As async parallel function takes array or object (in your example) of asynchronous tasks and these async tasks require a callback which will get called when its execution completes or if there is an error. So parallel function provides these callback functions and will call your callback ( provided as a second parameter to parallel function call) when all of them are called or got an error in any of them.
You can check the detailed explanation from here - https://caolan.github.io/async/v3/docs.html#parallel
Update:
Consider parallel as a wrapper function like:
function parallel(tasks, userCallback) {
let tasksDone = [];
function callback(err, data){ // this is the callback function you're asking for
if(err){
userCallback(err); // calling callback in case of error
}else {
tasksDone.push(data);
if(tasks.length === tasksDone.length){
userCallback(null, tasksDone); // calling callback when all the tasks finished
}
}
}
tasks.forEach(task => {
task(callback); // calling each task without waiting for previous one to finish
})
}
Note: This is not a proper implementation of parallel function of async, this is just an example to understand how we can use callback function internally and what is its usecase

callback is not a function in a mongodb query using node js´s async module

Im trying to call a function exported from another file inside a async each loop in which it iterates over an array of incoming data and executes queries accordingly like this :
const query = require('./queries')
function receive(req,resp ,doneFunc){
const listData = [];
let checkedList = req.body.checkedList
async.each(checkedList, (item, next)=>{
//Every iteration gets a parameter called action from an object inside the array which is the
//name of the function needed
//
query[item.action](req, res, resp, (err, data)=>{
listData.push(data);
if(listData.length === checkedList.length)doneFunc(listData);
next();
});
}, err=>{
console.log(err);
});
}
The function im calling in query[item.action] has the following structure
exports.any = function(res,callback){
MongoClient.connect(url,function (err, client) {
let dbo = client.db("DB")
if(!err) {
dbo.collection("collection",function(err ,coll){
coll.aggregate([
//make aggregation
]).toArray(function(err,data){
//execute callback when the aggregation has finished , this is where the error ocurrs
if(!err) return callback(null,data)
return callback(err,null)
})
})
} else {
return callback(err,null);
}
});
}
When the execution of the async.each loop reaches the call for query it returns the message
TypeError: callback is not a function
at ...\server\routes\queries.js:385:37
Which is where the return callback(null,data) is supposed to be executed.
What is the reason of this error , is the function set wrong or is it executed in a wrong way?
If I have understood your question (despite a lot of code missing to understand the question), this is the mistake:
function(res,callback){ // function signature
query[item.action](req, res, resp, (err, data)=>{ // function call
They do not match at all, you are passing res as callback and inside function you are using callback (which actually is res) as a function.

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

Parallel calls and wait condition in NodeJS with Q or Async

I'm trying to retrieve products basis by categories, I'd like to parallel the process, first I'm not able to figure out how to write wait condition or ideal a call back method to let parent function know that all products have been retrieved from database.
I'd be open to all solution, ideally here I have used Async but want to prefer Q (https://github.com/kriskowal/q/wiki/Examples-Gallery) which seems to be much better choice with Mongoose and MongoDB operations.
var temp = []
Async.each([1,2,3,4,5,6,7,8,9...n],
function (item, callback) {
database.getProductsByTaxonomy(item, function (err, products) {
temp = new Object();
temp.TaxonomyID = item;
temp.Products = products;
results.push(temp);
callback(err, products);
});
},
function (err) {
console.log(err);
});
<<wait for all .each call completes>>
return temp; // or callback (err, temp); // or emit?
Any solutions?
You can use my Qx library, which simplifies working with Q and arrays:
return Qx.map(arr, function (item) {
return getPromiseFromDb(item).then(function(products) {
return { taxonmyId: item, products: products };
});
});
If your DB uses callbacks rather than promises, you can use Q.ninvoke() to turn it into a promise.

Node.js - Using the async lib - async.foreach with object

I am using the node async lib - https://github.com/caolan/async#forEach and would like to iterate through an object and print out its index key. Once complete I would like execute a callback.
Here is what I have so far but the 'iterating done' is never seen:
async.forEach(Object.keys(dataObj), function (err, callback){
console.log('*****');
}, function() {
console.log('iterating done');
});
Why does the final function not get called?
How can I print the object index key?
The final function does not get called because async.forEach requires that you call the callback function for every element.
Use something like this:
async.forEach(Object.keys(dataObj), function (item, callback){
console.log(item); // print the key
// tell async that that particular element of the iterator is done
callback();
}, function(err) {
console.log('iterating done');
});
async.each is very useful and powerful function which is provided by Async Lib .it have 3 fields
1-collection/array
2- iteration
3-callback
the collection is referred to the array or collection of objects and iteration is refer to the each iteration and callback is optional .
if we are giving callback then it will return the response or say result which you want to show you in the frontend
Applies the function iteratee to each item in coll, in parallel. The iteratee is called with an item from the list, and a callback for when it has finished. If the iteratee passes an error to its callback, the main callback (for the each function) is immediately called with the error.
Note, that since this function applies iteratee to each item in parallel, there is no guarantee that the iteratee functions will complete in order.
exapmle-
var updateEventCredit = function ( userId, amount ,callback) {
async.each(userId, function(id, next) {
var incentiveData = new domain.incentive({
user_id:userId,
userName: id.userName,
amount: id.totalJeeneePrice,
description: id.description,
schemeType:id.schemeType
});
incentiveData.save(function (err, result) {
if (err) {
next(err);
} else {
domain.Events.findOneAndUpdate({
user_id: id.ids
}, {
$inc: {
eventsCredit: id.totalJeeneePrice
}
},{new:true}, function (err, result) {
if (err) {
Logger.info("Update status", err)
next(err);
} else {
Logger.info("Update status", result)
sendContributionNotification(id.ids,id.totalJeeneePrice);
next(null,null);
}
});
}
});

Resources