How to make a for statement wait multiple mongoose queries? - node.js

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));
})
});
});

Related

node.js async/sync with For Loop, Query, and Additional function

Hello I am having an issue with the following sequence, I need to run multiple queries which build on each other that are in a for loop then once the final result is obtained to implement the result. I am having an issue where my for loop is looping past the query, also I need to stop the code while the findX function is running.
I know this is an async problem but I don't see how I could chain promises, or use the async npm package with needing to loop queries that depend on the result of the previous query. Thanks in advance.
function findX(){
//executes another query
}
function solve(res, connection, info, requestArr, callback){
var parentID = null;
var obj = {};
for (var i = 0; i <= requestArr.length; i++) {
connection.query("SELECT WHERE ", [parentID, requestArr[i]], function(err, results) {
if(results[0]['x']){
var obj = findX(x)
break;
}else{
parentID = results[0]['parentID'];
}
});
}
//Do stuff with obj only after the final result has been set in the for loop
}
You can use Async TimesSeries.
// Pretend this is some complicated async factory
var createUser = function(id, callback) {
callback(null, {
id: 'user' + id
});
};
// generate 5 users
async.times(5, function(n, next) {
createUser(n, function(err, user) {
next(err, user);
});
}, function(err, users) {
// we should now have 5 users
});
So, in your example would be something like this:
var iterations = requestArr.length - 1,
parentID,
obj;
var getResults = function (i, callback) {
connection.query("SELECT WHERE ", [parentID, requestArr[i]], function (err, results) {
if (results[0]['x']) {
obj = findX(x);
callback('done');
}
else {
parentID = results[0]['parentID'];
callback();
}
});
};
async.timesSeries(iterations, function (n, next) {
getResults(n, function (err) {
next(err);
});
}, function (err) {
// use obj
});

nodeJS Mongoose updating multiple documents & returning the changed values

I'm essentially trying to find all Tasks with a given scaler_ID and then set them their respective scaler_ID to -1 and then return all of the new tasks in the HTTP response.
Here's my code:
api.post('/unassignTasks', function(req, res) {
Task.find({scaler_ID: req.body.scaler_ID}, function(err, tasks) {
for (var i = 0; i < tasks.length; ++i) {
tasks[i].scaler_Id = -1;
console.log(tasks[i]);
(function(arrayIndex) {
tasks[i].save(function(err, result) {
if (err) { console.log(err); }
});
})(i);
res.send(tasks);
return;
}
});
For some reason this won't work and the scaler_ID is not being set to -1 to the elements in the database. Any ideas what is causing this?
Proper way by using async.each
//npm install async --save //don't forget to install it first
var async = require('async');
api.post('/unassignTasks', function(req, res) {
var id = req.body.scaler_ID;
Task.find({scaler_ID: id }, function(err, tasks) {
async.each(tasks, function(task, each_cb) {
task.scaler_Id = -1;
task.save(each_cb);
}, function(err) {
if(err)
return res.status(400).send(err);
res.send(tasks);
});
});
}

mongodb native driver error on query

I am writing the filter using mongodb native driver, but it's driving me this error when you run the query.
In the case of this driver, it has no exec?
What is another way to perform this query?
exports.findAll = function(req, res) {
MongoClient.connect(url, function(err, db) {
var section = req.params.section;
var collection = db.collection(section);
var filter = req.query.filter ? {nameToLower: new RegExp('^' + req.query.filter.toLowerCase())} : {};
var query = collection.find(filter);
var count = 0;
collection.count(filter, function (error, result) {
count = result;
});
if(req.query.order) {
query.sort(req.query.order);
}
if(req.query.limit) {
query.limit(req.query.limit);
if(req.query.page) {
query.skip(req.query.limit * --req.query.page);
}
}
query.exec(function (error, results) {
res.json({
count: count,
data: results
});
});
});
};
Error:
TypeError: undefined is not a function
Better to use the async library in this case as it simplifies the code. In the case where you need to run multiple tasks that depend on each other and when they all finish do something else, use the
async.series() module. The following demonstrates how you can go about this in your case:
exports.findAll = function(req, res) {
var locals = {},
section = req.params.section,
filter = !!req.query.filter ? {nameToLower: new RegExp('^' + req.query.filter.toLowerCase())} : {};
async.series([
// Connect to DB
function(callback) {
MongoClient.connect(url, function(err, db) {
if (err) return callback(err);
locals.collection = db.collection(section); //Set the collection here, so the next task can access it
callback();
});
},
// Get count
function(callback) {
locals.collection.count(filter, function (err, result){
if (err) return callback(err);
locals.count = result; //Set the count here
callback();
});
},
// Query collection
function(callback) {
var cursor = locals.collection.find(filter);
if(req.query.order) {
cursor = cursor.sort(req.query.order);
}
if(req.query.limit) {
cursor = cursor.limit(req.query.limit);
if(req.query.page) {
cursor = cursor.skip(req.query.limit * --req.query.page);
}
}
cursor.toArray(function(err, docs) {
if (err) return callback(err);
locals.docs = docs;
callback();
});
}
], function(err) { //This function gets called after the three tasks have called their "task callbacks"
if (err) return next(err);
// Here locals will be populated with 'count' and 'docs'
res.json({
count: locals.count,
data: locals.docs
});
res.render('user-profile', locals);
});
};

how to run code sequentially in node js

Hi I have this code but when finish the result is not the espected because didn't run in the sequence that I wish
here is the code:
var user_data = {};
models.games.find({$or: [{w_id: req.user._id}, {b_id: req.user._id}, {owner: req.user._id}]}, function (err, games) {
var req_games = [];
if (!err) {
for (var i in games) {
req_games.push(games[i]);
models.users.findOne({_id: games[i].w_id}, function (err, user) {
req_games[i].w_id = user.user;
console.log(req_games[i].w_id) //< -- 3
});
console.log('a ' + req_games[i].w_id) //<-- 2
}
user_data.games = req_games; // <-- 1
}
});
at the end of the task req_games didnt have any update because it's running in the sequence that I put in the comments in the code
This may help you using Q(promises)
obj.find = function(model, condition) { //make your find to return promise
var deferred = q.defer();
model.find(condition, function(err, results) {
if (err) {
logger.log(err);
deferred.reject(err);
} else {
deferred.resolve(results);
}
});
return deferred.promise;
}
ArraysOfId.forEach(function (id) {
var tempProm = mongoUtilsMethodObj.find(schemaObj.Asset, id).then(function (assetObj) {
---- your code
return q.resolve();
});
promArr.push(tempProm);//push all promise to array
});
q.all(promArr).then(function () {
// this will be called when all promise in array will we resolved
})
Here is a version using the async library to map your game values.
var async = require('async');
var user_data = {};
models.games.find({$or: [{w_id: req.user._id}, {b_id: req.user._id}, {owner: req.user._id}]}, function (err, games) {
if(err) {
// or whatever your error response happens to be
return res.render('user.swig', {error: err});
}
async.map(games, function(game, nextGame) {
models.users.findOne({_id: game.w_id}, function (err, user) {
game.w_id = user.user;
nextGame(err, game);
});
}, function(err, req_games) {
user_data.games = req_games;
res.render('user.swig', {user: user_data});
});
});

Get monk to play well with Q.all

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");
});
});

Resources