Get monk to play well with Q.all - node.js

I have a collection of posts and a collection of users. When returning the list of posts, I want to resolve the references to users. This means making an async call for every row of the users. When monk returns a promise, it returns something that responds to "complete" or "success". Q expects something responding to "then". I need to use Q.all to wait for all the users to be fetched into the posts, but I can't make it play well with monk's promise style.
Here is my attempt.
exports.posts = function (req, res) {
req.posts.find()
.complete(function(err, posts) {
handle(err, res, posts);
var postsWithUsers = posts.map(function(post) {
return req.users.findOne({_id: post.userId}).complete(function(err, result) {
post.user = result;
});
});
Q.all(postsWithUsers.map(function(monkPromise) {
monkPromise.then = monkPromise.complete
}), function(err, results) {
console.log("done with all posts");
});
});
};

Just for everyone else out there. This is one solution, perhaps not the best.
exports.posts = function (req, res) {
req.posts.find()
.complete(function(err, posts) {
handle(err, res, posts);
var postsWithUsers = posts.map(function(post) {
var deferred = Q.defer();
return req.users.findOne({_id: post.userId}).complete(function(err, result) {
post.user = result;
deferred.resolve(result);
});
return deferred.promise;
});
Q.all(postsWithUsers, function(err, results) {
console.log("done with all posts");
});
});

Related

Node js foreach done function

Insted of using setTimeout, what should I use after foreach complete?
app.post('/grid', function(req, res){
getResults(req.body.idarray, function(callback){
res.send(callback);
});
});
function getResults(userIds, callback) {
var totalresult = [];
userIds.forEach(function (user) {
sequence
.then(function (next) {
db.query('SELECT given FROM books WHERE user_id = ?', [user.userId], function (err2, result) {
if (err2) throw err2;
next(err, result);
});
})
.then(function (next, err, books) {
db.query('SELECT received FROM encycs WHERE user_id = ?', [user.userId], function (err3, result2) {
if (err3) throw err3;
next(err, result2, books);
});
})
.then(function (next, err, books, encycs ) {
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
next();
});
});
setTimeout(function() {
console.log(totalresult); // output ok.
return callback(totalresult); // returning as expected
}, 2000);
}
I dont know what totalresult.length is. So i can't check the length.
So, according to your use case you need to call callback somehow and pass totalresult into it, because that what your external code, code in the route expected.
To do that, you can call callback before calling next of the third .then statement. Like that.
...
.then(function (next, err, books, encycs ) {
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
callback(totalresult);
next();
//console.log(totalresult); //output OK.
});
This might work.
Update 1
It is hard to follow with your code. Can't catch up the logic of it. I would propose you Promises approach. I prepared that solution, that might work. It might contain little errors, but it represents the main idea of what you are trying to achieve, and how it can be done.
app.post("/grid", (req, res) => {
getResults(req.body.idarray)
.then(data => {
res.status(200).json(data);
})
.catch(err => {
console.error("Error occured", err);
res.status(500).json(err);
});
});
function getResults(userIds) {
let promises = userIds.map(loadCalculation);
//this will wait until all loadings are done
return Promise.all(promises);
}
function loadCalculation(user) {
//parallel loading of the books and encycs
return Promise.all([loadBooks(user), loadEncycs(user)])
.then(results => {
let books = results[0];
let encycs = results[1];
let totalresult = [];
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
return totalresult;
});
}
function loadBooks(user) {
return makeQuery('SELECT given FROM books WHERE user_id = ?', user);
}
function loadEncycs(user) {
return makeQuery('SELECT received FROM encycs WHERE user_id = ?', user);
}
function makeQuery(query, user) {
return Promise((resolve, reject) => {
db.query(query, [user.userId], function (err, result) {
if(err) {
reject(err);
} else {
resolve(result);
}
});
});
}
Please, note that this is not really performant way to load the data from database, at least, I'm sure that you there is a possibility to load all the books and encycs with a single query, because you are using SQL, and it is really flexible language.

How do I return my data from a function?

I've written a small database function that gets some data from the DB and puts it into the format that I want, but I'm having difficulties returning the data to display it with Express. The database function is as follows:
function getAllEvents(req, res, next) {
db.any('select * from sensors, events where sensors.sensorid = events.sensorid')
.then(function (data) {
var final = [];
data.forEach(function(datas){
if (!final[datas.sensorid]){
final[datas.sensorid] = {};
}
if (!final[datas.sensorid].name){
final[datas.sensorid].name = datas.name;
final[datas.sensorid].signatures = {};
}
if (!final[datas.sensorid].signatures[datas.signature]){
final[datas.sensorid].signatures[datas.signature] = {};
final[datas.sensorid].signatures[datas.signature].id = "sen" + datas.sensorid + "sig" + datas.signature;
final[datas.sensorid].signatures[datas.signature].signature = datas.signature;
final[datas.sensorid].signatures[datas.signature].message = datas.message;
final[datas.sensorid].signatures[datas.signature].events = {};
}
final[datas.sensorid].signatures[datas.signature].events[datas.eventid] = datas;
})
return final;
})
.catch(function (err) {
console.log("Something went wrong! ", err)
});
}
And the router function to call it is this:
router.get('/events', function(req, res, next) {
db.getAllEvents(function(err, data){
res.render('events', { data: data });
});
});
I think the router function is waiting indefinitely for the data though as I get no errors but the page never loads. What am I doing wrong?
What am I doing wrong?
This code:
router.get('/events', function(req, res, next) {
db.getAllEvents(function(err, data){
res.render('events', { data: data });
});
});
is ok besides the fact that you are not checking for errors. Notice that getAllEvents is expecting a function as an argument.
now Let's look at your getAllEvents function prototype
function getAllEvents(req, res, next) {
It is simply not correct and should have been something like
function getAllEvents(callback) {
Then you would be able to call the callback and "return" the result like this
return callback(null,data);
or if an error occurred during you database connection pass the error to the callback
return callback(err);

Fetch from multiple, separate, collections with Express and MongoDB

I have a site where i need some data from my Mongo data to be shown. My problem, however, is that i need data from two collections. Collections that are completely separate and have nothing to do with each other.
Right now i have this in my routes for my profile-page:
router.get('/profile', function(req, res,next) {
var resultArray = [];
mongo.connect(url, function(err, db) {
var cursor = db.collection('users').find();
cursor.forEach(function(doc, err) {
resultArray.push(doc);
}, function() {
db.close();
res.render('profile/index', {users: resultArray});
});
});
});
And this, of course, works perfectly fine. But how do i get a second db.collection('colors').find(); to be passed along to my template too?
I'm sure it's something trivial, and me just not quite having the full grasp of things, but yeah.. I'm stuck..
Use the async library which is best suited for this scenario. Where you need to run multiple tasks that do not depend on each other and when they all finish do something else, you should use async.parallel() method. The signature is async.parallel(tasks, callback), where tasks is an array of functions.
It will immediately run all the functions in parallel, wait for all of them to call their task callback, and finally when all tasks are complete it will run callback (the final callback).
The following example demonstrates how this could be adapted for your use case:
router.get('/profile', function(req, res, next) {
mongo.connect(url, function(err, db) {
var locals = {};
var tasks = [
// Load users
function(callback) {
db.collection('users').find({}).toArray(function(err, users) {
if (err) return callback(err);
locals.users = users;
callback();
});
},
// Load colors
function(callback) {
db.collection('colors').find({}).toArray(function(err, colors) {
if (err) return callback(err);
locals.colors = colors;
callback();
});
}
];
async.parallel(tasks, function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, let express handle it by calling the `next` function
// Here `locals` will be an object with `users` and `colors` keys
// Example: `locals = {users: [...], colors: [...]}`
db.close();
res.render('profile/index', locals);
});
});
});
Try this code:
router.get('/profile', function(req, res,next) {
var resultArray = {
users : [],
colors : []
};
mongo.connect(url, function(err, db) {
var cursor = db.collection('users').find();
cursor.forEach(function(doc, err) {
resultArray.users.push(doc);
}
var colors = db.collection('colors').find();
colors.forEach(function(doc,err){
resultArray.colors.push(doc);
}, function() {
db.close();
res.render('profile/index', {users: resultArray.users, colors: resultArray.colors});
});
});
});
Didn't have time to check it, but I'm pretty sure that it would work.

How to make a for statement wait multiple mongoose queries?

What I am currently trying to do is to display all the playlists with the songs in it. To do that I first find every playlists, then I do a for to loop through them all (in the same time I initialize globalArr and put the values then it will be sended as json because it's an API) and the problem is when I do another find in the loop(PlaylistSong.find or Song.find) well since it's asynchronous the find will be made when the for will be over, and I will have 0 results because they will take the value of increment when he will be at his maximum. I heard of async, I even googled but I really don't understand how to put through this code because it's a combination of for loops and async queries...
Thanks for your help.
router.get('/', function(req, res, next) {
Playlist.find(function (err, playlists) {
if (err) return next(err);
/* Loop through every playlists */
var globalArr = [];
for (var increment = 0; increment < playlists.length; ++increment)
{
globalArr[increment] = [];
globalArr[increment]["name"] = playlists[increment].name;
/* Loop through every links between Songs and Playlist */
PlaylistSong.find({idPlaylist: playlists[increment]._id}, function (err, songs) {
if (err) return next(err);
for (var songIncrement = 0; songIncrement < songs.length; ++songIncrement) {
{
console.log("increment"+increment);
globalArr[increment][songIncrement] = [];
/* Getting the actual song by his ID */
Song.find({_id: song.idSong}, function (err, song) {
if (err) return next(err);
globalArr[increment][songIncrement]["name"] = songs[songIncrement].name;
globalArr[increment][songIncrement]["artist"] = songs[songIncrement].artist;
globalArr[increment][songIncrement]["picture"] = songs[songIncrement].picture;
globalArr[increment][songIncrement]["price"] = songs[songIncrement].price;
globalArr[increment][songIncrement]["file"] = songs[songIncrement].file;
globalArr[increment][songIncrement]["difficulty"] = songs[songIncrement].difficulty;
globalArr[increment][songIncrement]["downloaded"] = songs[songIncrement].downloaded;
});
}
}});
}
res.contentType('application/json');
res.send(JSON.stringify(globalArr));
});
});
See this question and the accepted answer:
Simplest way to wait some asynchronous tasks complete, in Javascript?
It basically says to use the Async module, push all of your async function calls onto it and then use async.parallel() which gives you a callback when all of the async functions have completed.
I haven't tested it, but something like this seems like it might work:
var async = require('async');
var calls = [];
router.get('/', function(req, res, next) {
Playlist.find(function (err, playlists) {
if (err) return next(err);
/* Loop through every playlists */
var globalArr = [];
for (var increment = 0; increment < playlists.length; ++increment)
{
(function() {
var i = increment;
calls.push(function(callback) {
globalArr[i] = [];
globalArr[i]["name"] = playlists[i].name;
/* Loop through every links between Songs and Playlist */
PlaylistSong.find({idPlaylist: playlists[increment]._id}, function (err, songs) {
if (err) return next(err);
for (var songIncrement = 0; songIncrement < songs.length; ++songIncrement) {
{
console.log("increment"+i);
globalArr[i][songIncrement] = [];
/* Getting the actual song by his ID */
Song.find({_id: song.idSong}, function (err, song) {
if (err) return next(err);
globalArr[i][songIncrement]["name"] = songs[songIncrement].name;
globalArr[i][songIncrement]["artist"] = songs[songIncrement].artist;
globalArr[i][songIncrement]["picture"] = songs[songIncrement].picture;
globalArr[i][songIncrement]["price"] = songs[songIncrement].price;
globalArr[i][songIncrement]["file"] = songs[songIncrement].file;
globalArr[i][songIncrement]["difficulty"] = songs[songIncrement].difficulty;
globalArr[i][songIncrement]["downloaded"] = songs[songIncrement].downloaded;
});
}
callback();
}});
});
})();
}
async.parallel(calls, function(err, result) {
if (err) {
// TODO: Handle error here
}
res.contentType('application/json');
res.send(JSON.stringify(globalArr));
});
});
});
or if you don't want then to execute in parallel, you can use async.series() instead.
See this jsFiddle for a simplified example of your situation... https://jsfiddle.net/bpursley/fj22hf6g/
Yeah, you should use async. I'll explain this in more detail later (son must go to bed...)
PlaylistSong.statics.findSongsByPlaylistId = function(id, done) {
PlaylistSong.find({idPlaylist: id}, function(err, songs) {
if (err) {
done(err)
return
}
var getSongsFns = songs.map(function(song) {
return function(callback) {
Song.find({_id: song.idSong}, callback)
}
})
async.parallel(getSongsFns, done)
})
}
router.get('/', function(req, res, next) {
Playlist.find(function (err, playlists) {
if (err) return next(err);
var getSongsFns = playlists.map(function(playlist) {
return function(callback) {
PlaylistSong.findSongsByPlaylistId(playlist._id, callback)
}
})
async.parallel(getSongsFns, function(err, songs) {
if (err) {
res.status(500).send()
return
}
res.contentType('application/json');
res.send(JSON.stringify(songs));
})
});
});

call back inside callback in nodejs?

I have following nodejs code:
app.get('/dashboard', function(req, res){
db.collection('com_url_mamy').find().toArray(function(err, doc){
db.collection('com_url_mamy').find({'price':''}).count(function(err, docs){
db.collection('com_url_mamy').find({"price":{$not:{$ne:"last_price_1"}}}).count(function(err, last_doc){
if(err){console.log(err);}
console.log(docs);
res.render('dashboard',{'doc':doc, 'docs':docs, 'last_doc':last_doc});
});
});
});
Here i have to add more two or three query/callback.
But I don't think this is right way to do.
Please any one can me tell me how I can solve this problem to increase performance.
Thank You
Using async/await will be an appropriate solution that avoids the callback hell for you in this case. Consider running your queries as follows:
app.get('/user/:name', async (req, res, next) => {
try {
const docs = await db.collection('com_url_mamy').find().toArray()
const count = await db.collection('com_url_mamy').find({'price':''}).count()
const last_doc = await db.collection('com_url_mamy').find({"price": "last_price_1"}).count()
res.render('dashboard', { docs, count, last_doc })
} catch (err) {
return next(err)
}
}
As an alternative, you can use the async libary especially the async.parallel() method when you need to run multiple tasks that do not depend on each other and when they all finish do something else.
Consider the following example:
app.get('/user/:name', function(req, res, next) {
var locals = {};
async.parallel([
// Load all documents
function(callback) {
db.collection('com_url_mamy').find().toArray(function(err, docs){
if (err) return callback(err);
locals.docs = docs;
callback();
});
},
// Get count of documents where price is empty
function(callback) {
db.collection('com_url_mamy').find({'price':''}).count(function(err, count){
if (err) return callback(err);
locals.count = count;
callback();
});
},
// Load last docs
function(callback) {
db.collection('com_url_mamy').find({"price": "last_price_1"}).count(function(err, docs){
if (err) return callback(err);
locals.last_doc = docs;
callback();
});
}
], function(err) { //This function gets called after the three tasks have called their "task callbacks"
if (err) return next(err);
//Here render dashboard with locals object
res.render('dashboard', locals);
});
});
You can use native Promises with MongoDB driver (on node.js >= 0.12):
app.get('/dashboard', function(req, res){
var proms = [];
proms.push(db.collection('com_url_mamy').find().toArray());
proms.push(db.collection('com_url_mamy').find({'price':''}).count());
proms.push(db.collection('com_url_mamy').find({"price": "last_price_1"}).count());
Promise.all(proms)
.then(function(pres) {
res.render('dashboard',{'doc':pres[0], 'docs': pres[1], 'last_doc': pres[2]});
})
.catch(console.error);
});
Promise.all takes the promises you give it and execute them in parallel.
The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved
Source:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
(BTW I think you should rename your question with something more like 'Improve independent nested async calls' to avoid the closing/duplicate issue you had.)

Resources