I try to store the result of a DB query in an array but I always got an empty array. I don't understand very well how Async works but I think this code should be work because I store The variable before It finish
Note: following is an array also and I understand this problem is because Async behavior but I dont know what to do to solve it
code:
exports.getfollowingUser = function(req, res){
followedUser=[];
following = req.user.follow;
for (i = 0; i < following.length; i++) {
User.find(following[i].followed, function (err, followUser){
followedUser[i]= followUser;
});
}
console.log(followedUser) // result empty
res.render('following', {
followedUser: followedUser
});
};
EDIT :1 Follow schema
module.exports = mongoose.model('Friendship',{
follower: String,
followed: String
});
EDIT :2 User schema
module.exports = mongoose.model('User',{
email:{
type: String,
unique: true,
lowercase: true },
password:String,
profile:{
fullname: String,
gender: String,
role: {type: String, default: 'Autorizado'},
country: String },
});
Note: I'm trying to get the friends that the User (Login) is following and show them in a view.
In your for loop node just make request to your DB and continue processing. It doesn't wait for result from database. This is how async works. So after your for loop node didn't yet receive results from DB and followedUser is empty. To fix this without third-part libraries you can do:
exports.getfollowingUser = function(req, res){
findFollowedUser(req.user.follow, function(error, followedUser) {
console.log(followedUser);
res.render('following', {
followedUser: followedUser
});
});
function findFollowedUser(following, callback) {
var followedUser=[];
var waiting = following.length;
var wasError = false;
for (var i = 0; i < following.length; i++) {
User.find(following[i].followed, function (err, followUser){
if (wasError) {
return;
}
if (err) {
wasError = err;
return callback(err);
}
followedUser[i]= followUser;
waiting--;
if (!waiting) {
callback(null, followedUser);
}
});
}
}
};
Another way (and I think better) is to use some flow control libraries. For example:
q
bluebird
async
fibers
My personal preference: bluebird - extremely fast promise realization. Actually promises is upcoming standard of javascript. So I'd recommend you to take a closer look at it.
Also I'd recommend you to watch this video. This is very simplified explanation of javascript async model (how event loop works).
Look at the flow of execution:
exports.getfollowingUser = function(req, res){
followedUser=[]; // 1
following = req.user.follow;
for (i = 0; i < following.length; i++) {
User.find(following[i].followed, function (err, followUser){ // 2,3,4
followedUser[i]= followUser; // 7,8,9
});
}
console.log(followedUser) // result empty // 5
res.render('following', {followedUser: followedUser}); // 6
};
As you see, at the time of your console.log [5], followedUser[i]= followUser hasn't yet executed [7,8,9]. This is why you're getting the empty array.
Here's how you could fix it
exports.getfollowingUser = function(req, res){
followedUser=[]; // 1
following = req.user.follow;
var counter = 0;
var length = following.length;
for (i = 0; i < following.length; i++) {
User.find(following[i].followed, function (err, followUser){ // 2,3,4
followedUser[i]= followUser; // 7,8,9
counter++;
if(counter>=length) allDone(); // 10
});
}
console.log(followedUser) // result empty // 5
res.render('following', {followedUser: followedUser}); // 6
function allDone(){
console.log(followedUser) // result empty // 11
res.render('following', {followedUser: followedUser}); // 12
}
};
Here the same statements but inside the allDone()[11,12] will do what you're expecting them to do, as they execute after [7,8,9].
This is just to explain, there are better libraries to handle stuff like this:
Async: https://github.com/caolan/async#each
Promises: https://gist.github.com/neilk/8467412
Also helpful: https://blog.engineyard.com/2015/taming-asynchronous-javascript-with-async
Related
I've been puzzling over this loop for a few days and can't quite get to what I need.
I loop over my array of search results (mdb_results) and extract from each object a .name to use as a _search term in a Google CSE Image Search.
Th CSE returns another array (cse) which I want to append to the object from which I extracted the _search term (mdb_results[i]).
router.get('/json', function(req, res, next) {
var ImageSearch = require('node-google-image-search');
MongoClient.connect(url, function(err,db){
db.collection('mycatalog')
.find({$text: {$search:"FOO" }})
.toArray(function(err, mdb_results){
for (var i=0; i<mdb_results.length; i++){
var _search = mdb_results[i].name ;
ImageSearch(_search, function(cse){
// How to add cse.img to mdb_results[i].images ??
// mdb_results[i].images = cse;
// gives undefined
},0,2);
};
res.send(mdb_results);
});
});
});
My initial mdb_results looks like this.
[{"name":"FOO"},{"name":"FOOOO"}]
I'm trying to achieve something like this,
[{"name":"FOO",
"images":[{"img1":"http://thumbnail1"},{"img2":"http://thumbnail2"}]
},
{"name":"FOOOO",
"images":[{"img1":"http://thumbnaila"},{"img2":"http://thumbnailb"}]
}]
Can anyone show me how to achieve this?
Thanks
The issue is that you're expecting an asynchronous operation to work synchronously:
router.get('/json', function(req, res, next) {
var ImageSearch = require('node-google-image-search');
MongoClient.connect(url, function(err,db){
db.collection('mycatalog')
.find({$text: {$search:"FOO" }})
.toArray(function(err, mdb_results){
for (var i=0; i<mdb_results.length; i++){
var _search = mdb_results[i].name ;
// This search is asynchronous, it won't have returned by the time
// you return the result below.
ImageSearch(_search, function(cse){
// How to add cse.img to mdb_results[i].images ??
// mdb_results[i].images = cse;
// gives undefined
},0,2);
};
// At this point, ImageSearch has been called, but has not returned results.
res.send(mdb_results);
});
});
});
You're going to need to use promises or the async library.
Here's an example:
router.get('/json', function(req, res, next) {
var ImageSearch = require('node-google-image-search');
MongoClient.connect(url, function(err,db){
db.collection('mycatalog')
.find({$text: {$search:"FOO" }})
.toArray(function(err, mdb_results){
// async.forEach will iterate through an array and perform an asynchronous action.
// It waits for you to call callback() to indicate that you are done
// rather than waiting for it to execute synchronously.
async.forEach(mdb_results, function (result, callback) {
ImageSearch(result.name, function(cse){
// This code doesn't get executed until the network call returns results.
result.images = cse;
// This indicate that you are done with this iteration.
return callback();
},0,2);
},
// This gets call once callback() is called for each element in the array,
// so this only gets fired after ImageSearch returns you results.
function (err) {
res.send(mdb_results);
});
});
});
I'm trying to reach different select's from another select with MongoDB database with mongoose to redirect to Emberjs front-end.
If the text above it's not clear, look at the schema of the database:
// I already get the exam content given an id
Exam:{
...
collections:[{
question: Object(Id)
}]
...
}
and in the question schema it's:
// I want to get content of this giving an id
question:{
...
questions: String, // I want to get this given an Id exam
value: Number // and this
...
}
I tried to get it getting the objects id of the collections and then make a for to extract each question, and save the returned values into a json variable like this:
Test.findById(id, 'collections', function(err, collections){
if(err)
{
res.send(err);
}
var result ={}; //json variable for the response
// the for it's with the number of objectId's that had been found
for(var i = 0; i < collections.collections.length; i++)
{
// Send the request's to the database
QuestionGroup.findById(collections.collections[i].id, 'questions').exec(function(err, questiongroup)
{
if(err)
{
res.send(err);
}
// save the results in the json
result.questiongroup = questiongroup;
// prints questions
console.log(result);
});
// it return's {}
console.log(result);
}
// also return {}
console.log(result);
res.json({result: result});
});
It's there a way to save the requests into a variable and return it like a json to the front end?
since the query within the loop executes in async manner you'll have send response once everything finished execution.
For eg.
Test.findById(id, 'collections', function(err, collections) {
if (err) {
res.send(err);
}
var result = []; //json variable for the response
function done(result) {
res.json({
result
});
}
for (var i = 0, l = collections.collections.length; i < l; i++) {
// i need needs to be in private scope
(function(i) {
QuestionGroup.findById(collections.collections[i].id, 'questions').exec(function(err, questiongroup) {
if (err) {
res.send(err);
}
// save the results in the json
result.push(questiongroup);
if (l === i + 1) {
done(result);
}
});
})(i);
}
});
NOTE: untested, and you might have to handle errors appropriately
var veh = [];
app.get('/updateProf', isLoggedIn, function(req, res) {
for (var i = 0; i < req.user.local.vehicles.length; i++){
Vehicles.findById(req.user.local.vehicles[i], function(err, vehicle) {
veh.push(vehicle);
console.log("GET Json: " + veh);
});
}
console.log(veh);
res.json(veh);
veh.length = 0;
});
So I am doing a get request to obtain all my vehicles that a user owns and return its json, it works fine after a refresh, but when I go to the page it shows a empty array on the initial load, if I refresh the page, the array is populated. I think the issue is something to do with it being asynchronous but I'm having a hard time thinking this way and need some advice on how to tackle this.
Yes!!
You will have to wait for all of the callbacks to finish before returning the JSON.
A solution is to keep a count of how many callbacks have been executed and when all of them have been executed you can return the JSON.
var veh = [];
app.get('/updateProf', isLoggedIn, function(req, res) {
number_processed = 0;
total = req.user.local.vehicles.length;
for (var i = 0; i < req.user.local.vehicles.length; i++){
Vehicles.findById(req.user.local.vehicles[i], function(err, vehicle) {
if(!err){
veh.push(vehicle);
}
number_processed = number_processed + 1;
if(number_processed === total){
res.json(veh);
}
console.log("GET JSON: " + veh);
});
}
veh.length = 0;
});
If you are using a more recent version of Mongoose, then you can directly use Promises that are returned by Mongoose for each query.
For example, your query can be simplified to
Vehicles.find({ _id: {'$in': req.user.local.vehicles }})
.exec()
.then(function(vehicleArr) {
res.json(vehicleArr);
});
Note that I use the $in operator to directly translate your loop into an IN condition, which takes an array of what you want to compare (in this case, it's an array of IDs)
The then() function just executes on completion of the query.
Async is a utility library to deal with this.
var async = require('async');
app.get('/updateProf', isLoggedIn, function(req, res) {
async.map(req.user.local.vehicles, function(vehicle, cb){
Vehicles.findById(vehicle, function(err, vehicle) {
if (err) cb(err, null);
console.log('GET Json: ' + vehicle);
cb(null, vehicle);
});
}, function (err, results) {
console.log(results);
res.json(results);
});
});
I want to query some data from the database, from a for loop. The problem is, the query is processed after the end of the loop.
This code:
ret = [];
first = rows[0];
first.device_firsttime = first.device_lasttime;
first.alerts = [];
for(var i = 0; i < 5; i++)
{
console.log("!"+i);
(function(i) {
query("SELECT EXTRACT('epoch' FROM alert_time)::integer alert_time, alert_id, alert_lat, alert_lon, alert_str, alert_cc, alert_distance FROM blitz_device_former_alerts WHERE alert_locid = $1", [first.device_locid], function(error_a,rows_a,result_a)
{
console.log(i+"!");
ret.push(i);
});
})(i);
}
console.log("-END---"+JSON.stringify(ret));
ret.push(first);
res.end(JSON.stringify(ret));
Writing this to the console:
!0
!1
!2
!3
!4
-END---[]
POST /userlogs 200 140.110 ms - -
0!
1!
2!
3!
4!
The correct output should be
!0
0!
!1
1!
!2
2!
!3
3!
!4
4!
-END---[0,1,2,3,4]
POST /userlogs 200 xxx.xxx ms - -
The query is asynchronous. The loop doesn't wait for those queries to finish. It's easier to use some flow control library such as 'async'.
var async = require('async');
async.eachSeries([0, 1, 2, 3, 4], function(i, callback) {
console.log("!"+i);
query("SELECT EXTRACT('epoch' FROM alert_time)::integer alert_time, alert_id, alert_lat, alert_lon, alert_str, alert_cc, alert_distance FROM blitz_device_former_alerts WHERE alert_locid = $1", [first.device_locid], function(error_a,rows_a,result_a) {
console.log(i+"!");
ret.push(i);
callback(null); // null -> no error
});
}, function(err) {
console.log("-END---"+JSON.stringify(ret));
ret.push(first);
res.end(JSON.stringify(ret));
});
You may want to use the async library for that job: essentially it will run your functions in sequence. It is also a good practice to use named functions, in this case a second-order function getExtractor(): a function that returns another function, usable by async. async likes to have functions that accept callbacks, which is where the result of each execution is added.
The code would be something like this:
var async = require('async');
first = rows[0];
first.device_firsttime = first.device_lasttime;
first.alerts = [];
var tasks = [];
for(var i = 0; i < 5; i++)
{
tasks.push(getExtractor(i));
}
async.series(function(result) {
console.log("-END---"+JSON.stringify(result));
result.push(first);
res.end(JSON.stringify(result));
});
function getExtractor(i) {
return function(callback) {
console.log("!"+i);
query("SELECT EXTRACT('epoch' FROM alert_time)::integer alert_time, alert_id, alert_lat, alert_lon, alert_str, alert_cc, alert_distance FROM blitz_device_former_alerts WHERE alert_locid = $1", [first.device_locid], function(error_a,rows_a,result_a)
{
console.log(i+"!");
return callback(null, i);
});
});
}
A few notes:
Each function pushed to the tasks array accepts a callback of the form function(error, result). The first parameter is an optional error.
I have not dealt with errors in the code, for simplicity, but it is important to do it.
If for any reason you want to run all functions in parallel you can do it just using async.parallel().
The final bit (when all pieces have been gathered) is executed in the callback passed to async.series().
When async receives an array of functions, it sends to its callback an array of results. That is why we get the result automatically constructed by async.
You can do the same without async but frankly, the code is quite complex and I don't recommend it.
Below is a snapshot of my code from routes/index.js
exports.index = function(req, res){
var results=new Array();
for(var i=0; i<1000;i++){
//do database query or time intensive task here based on i
// add each result to the results array
}
res.render('index', { title: 'Home !' , results:results });
};
If I run this code , due to the asynchronous nature of javascript the last line gets executed before the loop gets completely processed . Hence my webpage doesnt have the results . How do I structure this such a way that the page gets loaded once the queries are completed ?
Updated
Inside the loop I have database code ( Redis ) like the below -
client.hgetall("game:" +i, function(err, reply) {
results.push(reply.name);
});
Use the async library:
exports.index = function(req, res){
var results=new Array();
async.forEach(someArray, function(item, callback){
// call this callback when any asynchronous processing is done and
// this iteration of the loop can be considered complete
callback();
// function to run after loop has completed
}, function(err) {
if ( !err) res.render('index', { title: 'Home !' , results:results });
});
};
If one of the tasks inside the loop is asynchronous, you need to pass the asynchronous task a callback that calls callback(). If you don't have an array to use in forEach, just populate one with integers 1-1000.
Edit: Given your latest code, just put the async callback() right after responses.push(reply.name).
exports.index = function(req, res) {
var events = require("events");
var e = new events.EventEmitter();
e.expected = 1000;
e.finished = 0;
e.results = [];
e.on("finishedQuery", (function(err, r) {
this.finished += 1;
this.results.push(r && r.name);
if (this.finished === this.expected) {
res.render('index', { title: 'Home !' , results:this.results });
};
}).bind(e));
for (var i = 0; i < e.expected; i++) {
client.hgetall("game:" + i, function(err, reply) {
e.emit("finishedQuery", err, reply);
});
};
};
Of course, the above code doesn't handle [1 or more] errors. You'd need to add logic that only responds (1) on the first error or (2) if no errors occur.