Populate an array within an asynchronous function in Node.js - javascript

I recently started with Mongoose and Node.js and I have some difficulties in handling the result of an asynchronous Mongoose query. I have a collection called 'Groups' and a group can hold members located in a seperate collection called 'Members'. To accomplish this each group has a field named 'members' which is an array of ObjectId's. In addition each member has a profile which is refered to by an ObjectId. So each member has a field named 'profile' and the profiles are stored in a collection called 'Profiles'.
Now I want to get a list of profiles in a given group. Therefore I wrote this code:
// Function that returns a profile by a given objectId
function getprofile(profileId, profile) {
Profile
.findById(profileId)
.exec(function(err, res) {
if(err) { return null; }
if(!res) { return null; }
return res;
});
}
// Main function that returns a list of all (unique) profiles from members within a group
Group
.findById(req.params.id)
.populate('members')
.exec(function(err, group) {
if(err) { return handleError(res, err); }
var tmpProfiles = [];
var allProfiles = [];
for(var i=0; i<group.members.length; i++) {
var memberProfile = group.members[i].profile;
// Check if the member's profile is already in the array
if(objectIndexOf(tmpProfiles, memberProfile) == -1) {
tmpProfiles.push(memberProfile);
allProfiles.push(getprofile(memberProfile)); // add the profile to the array of profiles
}
}
return res.json(allProfiles); // this returns an empty array
});
The problem is that the 'Profile.findById' function and the 'Group.findById' function are both asynchronous and for this reason the function returns an array with only 'null' values. I know that I can solve this by using the 'Async' library but that is not an option for me so I have to go through the 'callback hell'. Can someone put me in the right direction by solving this problem? I already found some solutions which uses recursive calling of functions but I have no idea how to implement it in this case.

You can take advantage of the fact that mongoose methods return Promises.
Note: you will need to either be using Node >= v0.12 or have a Promise library required in the module.
// Function that returns a profile by a given objectId
function getprofile(profileId, profile) {
return Profile.findById(profileId); // return the Promise of this `findById` call
}
// Main function that returns a list of all (unique) profiles from members within a group
Group
.findById(req.params.id)
.populate('members')
.then(function(group) {
var tmpProfiles = [];
var promises = []; // store all promises from `getprofile` calls
for(var i = 0; i < group.members.length; i++) {
var memberProfile = group.members[i].profile;
// Check if the member's profile is already in the array
if(objectIndexOf(tmpProfiles, memberProfile) == -1) {
tmpProfiles.push(memberProfile);
promises.push(getprofile(memberProfile));
}
}
return Promise.all(promises);
})
.then(function(allProfiles) {
res.json(allProfiles);
})
.catch(function(err) {
//todo: handle error
console.log(err);
});

I support your learning by doing callback hell without caolan/async!
Here's an async answer anyway, to compare to your OP and the promises answer
Group...exec(function(err, group){
async.waterfall([
function(done1){
var tmpUnique = [];
async.filter(group.members, function(member, done2){
var isUnique = (tmpUnique.indexOf(member.profile) === -1);
if(isUnique){ tmpUnique.push(member.profile) };
done2(isUnique);
}, function(err, uniqueMembers){
done1(err, uniqueMembers);
})
},
function(uniqueMembers, done1){
async.map(uniqueMembers, function(member, done2){
getProfile(member.profile, done2);
// rewrite getProfile(id, callback){} to callback(err, profile)
// and maybe rename the key member.profile if its an id to get the real profile?
}, function(err, uniqueProfiles){
done1(err, uniqueProfiles)
});
},
], function(err, uniqueProfiles){
//callback(err, uniqueProfiles) or do something further
})
})

Related

returning data from for loop

i am using socket.io to fetch data about a user using his uid when you run this function
function getUserData(uid){
"use strict"
socket.emit('getUserData',uid, function(callback){
console.log('callback' + callback)
for (var i = 0; i < callback.length; i++) {
var row = callback[i];
var username = row.username;
var about = row.about;
var uid = row.uid;
}
})
return {
username: username,
uid: uid,
// about: about
};
}
and it does this on the server side
socket.on('getUserData',function(uid, callback){
connection.query('SELECT * FROM users WHERE uid = ?', [uid], function(err, rows) {
callback(rows)
})
})
but when i do console.log(getUserData(uid)) i get undefined but i do get the object from the first callback what am i doing wrong here?
The callback from .emit() is asynchronous. That means it happens sometime LATER, long after your getUserData() function has already returned. That means you have to communicate back the result using either a callback or a promise. In addition, it makes no sense that you're trying to iterate an array and return one result. You should either return all the results or pick one particular item from the array as your final value. This resolves with the whole array of data, letting the caller decide which one they want to pick from the array.
Here's how you could do so with a promise:
function getUserData(uid){
"use strict"
return new Promise(resolve => {
socket.emit('getUserData',uid, function(returnData){
console.log('returnData', returnData)
resolve(returnData);
});
});
}
// usage
getUserData(someUID).then(results => {
// use results in here
});

how to get all keys and values in redis in javascript?

I am creating a node API using javascript. I have used redis as my key value store.
I created a redis-client in my app and am able to get values for perticular key.
I want to retrieve all keys along with their values.
So Far I have done this :
app.get('/jobs', function (req, res) {
var jobs = [];
client.keys('*', function (err, keys) {
if (err) return console.log(err);
if(keys){
for(var i=0;i<keys.length;i++){
client.get(keys[i], function (error, value) {
if (err) return console.log(err);
var job = {};
job['jobId']=keys[i];
job['data']=value;
jobs.push(job);
});
}
console.log(jobs);
res.json({data:jobs});
}
});
});
but I always get blank array in response.
is there any way to do this in javascript?
Thanks
First of all, the issue in your question is that, inside the for loop, client.get is invoked with an asynchronous callback where the synchronous for loop will not wait for the asynchronous callback and hence the next line res.json({data:jobs}); is getting called immediately after the for loop before the asynchronous callbacks. At the time of the line res.json({data:jobs}); is getting invoked, the array jobs is still empty [] and getting returned with the response.
To mitigate this, you should use any promise modules like async, bluebird, ES6 Promise etc.
Modified code using async module,
app.get('/jobs', function (req, res) {
var jobs = [];
client.keys('*', function (err, keys) {
if (err) return console.log(err);
if(keys){
async.map(keys, function(key, cb) {
client.get(key, function (error, value) {
if (error) return cb(error);
var job = {};
job['jobId']=key;
job['data']=value;
cb(null, job);
});
}, function (error, results) {
if (error) return console.log(error);
console.log(results);
res.json({data:results});
});
}
});
});
But from the Redis documentation, it is observed that usage of
Keys are intended for debugging and special operations, such as
changing your keyspace layout and not advisable to production
environments.
Hence, I would suggest using another module called redisscan as below which uses SCAN instead of KEYS as suggested in the Redis documentation.
Something like,
var redisScan = require('redisscan');
var redis = require('redis').createClient();
redisScan({
redis: redis,
each_callback: function (type, key, subkey, value, cb) {
console.log(type, key, subkey, value);
cb();
},
done_callback: function (err) {
console.log("-=-=-=-=-=--=-=-=-");
redis.quit();
}
});
Combination of 2 requests:
import * as ioredis from 'ioredis';
const redis = new ioredis({
port: redisPort,
host: redisServer,
password: '',
db: 0
});
const keys = await redis.collection.keys('*');
const values = await redis.collection.mget(keys);
Order will be the same for both arrays.
This will get all keys but with no values:
const redis = require('redis');
const client = redis.createClient();
client.keys('*', (err, keys) => {
// ...
});
Now you need to get the values for those keys in a usual way. For example:
Promise.all(keys.map(key => client.getAsync(key))).then(values => {
// ...
});
or with async module or in any way you like.
You should never do this. First off, it is not recommended to use KEYS * in production. Second, this does not scale (cluster).
You can organise your cached entries into SETs and query for the items within the SET, then retrieve the references keys. This also makes invalidation easier.
Have a look at some data storage best practices.
https://redis.io/topics/data-types-intro
how to get all keys and values in redis in javascript?
You may find something useful in this link
https://github.com/NodeRedis/node_redis/tree/master/examples

Node for-loop callback required

I want to change the Tags format which I am fetching form one of the collections. Tags data contains some KC ids in an array which I am using to get KC data and insert in TagUnit to get final response format.
var newTags = Tags.map(function(TagUnit) {
for (var i = 0; i < TagUnit.kcs.length; i++) {
KCArray = [];
KC.findById(TagUnit.kcs[i], function(error, data) {
KCMap = {};
KCMap['kc_id'] = data._id;
KCMap['kc_title'] = data.title;
KCArray.push(KCMap);
if (KCArray.length == TagUnit.kcs.length) {
TagUnit.kcs = KCArray;
}
});
}
return TagUnit;
});
response.send(JSON.stringify(newTags));
But I am not getting desired result. Response is giving out Tag data in initial for instead of formatted form. I guess it is due to event looping. I will be grateful if someone can help me with this.
**Edit: ** I am using MongoDB as database and mongoose as ORM.
I'd suggest using promises to manage your async operations which is now the standard in ES6. You don't say what database you're using (it may already have a promise-based interface). If it doesn't, then we manually promisify KC.findById():
function findById(key) {
return new Promise(function(resolve, reject) {
KC.findById(key, function(err, data) {
if (err) return reject(err);
resolve(data);
});
});
}
Then, assuming you can do all these find operations in parallel, you can use Promise.all() to both keep track of when they are all done and to order them for you.
var allPromises = Tags.map(function(TagUnit) {
var promises = TagUnit.kcs.map(function(key) {
return findById(key).then(function(data) {
// make resolved value be this object
return {kc_id: data._id, kc_title: data.title};
});
});
// this returns a promise that resolves with an array of the kc_id and kc_title objects
return Promise.all(promises).then(function(results) {
return {
_id: TagUnit._id,
kcs: results
};
});
});
// now see when they are all done
Promise.all(allPromises).then(function(results) {
response.send(JSON.stringify(results));
}).catch(function(err) {
// send error response here
});
You can use promises or Async module
var async = require('async');
...
function getKC (kc, callback) {
KC.findById(kc, function(err, data) {
if (err)
return callback(err);
callback(null, {kc_id: data._id, kc_title: data.title})
});
}
function getKCs (tag, callback) {
async.map(tag.kcs, getKC, callback);
}
async.map(Tags, getKCs, function(err, results){
if (err)
return console.log(err.message);
res.json(results); // or modify and send
});
P.S. Perhaps, code contains errors. I cann't test it.

Node.JS How to set a variable outside the current scope

I have some code that I cant get my head around, I am trying to return an array of object using a callback, I have a function that is returning the values and then pushing them into an array but I cant access this outside of the function, I am doing something stupid here but can't tell what ( I am very new to Node.JS )
for (var index in res.response.result) {
var marketArray = [];
(function () {
var market = res.response.result[index];
createOrUpdateMarket(market, eventObj , function (err, marketObj) {
marketArray.push(marketObj)
console.log('The Array is %s',marketArray.length) //Returns The Array is 1.2.3..etc
});
console.log('The Array is %s',marketArray.length) // Returns The Array is 0
})();
}
You have multiple issues going on here. A core issue is to gain an understanding of how asynchronous responses work and which code executes when. But, in addition to that you also have to learn how to manage multiple async responses in a loop and how to know when all the responses are done and how to get the results in order and what tools can best be used in node.js to do that.
Your core issue is a matter of timing. The createOrUpdateMarket() function is probably asynchronous. That means that it starts its operation when the function is called, then calls its callback sometime in the future. Meanwhile the rest of your code continues to run. Thus, you are trying to access the array BEFORE the callback has been called.
Because you cannot know exactly when that callback will be called, the only place you can reliably use the callback data is inside the callback or in something that is called from within the callback.
You can read more about the details of the async/callback issue here: Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
To know when a whole series of these createOrUpdateMarket() operations are all done, you will have to code especially to know when all of them are done and you cannot rely on a simple for loop. The modern way to do that is to use promises which offer tools for helping you manage the timing of one or more asynchronous operations.
In addition, if you want to accumulate results from your for loop in marketArray, you have to declare and initialize that before your for loop, not inside your for loop. Here are several solutions:
Manually Coded Solution
var len = res.response.result.length;
var marketArray = new Array(len), cntr = 0;
for (var index = 0, index < len; index++) {
(function(i) {
createOrUpdateMarket(res.response.result[i], eventObj , function (err, marketObj) {
++cntr;
if (err) {
// need error handling here
}
marketArray[i] = marketObj;
// if last response has just finished
if (cntr === len) {
// here the marketArray is fully populated and all responses are done
// put your code to process the marketArray here
}
});
})(index);
}
Standard Promises Built Into Node.js
// make a version of createOrUpdateMarket that returns a promise
function createOrUpdateMarketAsync(a, b) {
return new Promise(function(resolve, reject) {
createOrUpdateMarket(a, b, function(err, marketObj) {
if (err) {
reject(err);
return;
}
resolve(marketObj);
});
});
}
var promises = [];
for (var i = 0; i < res.response.result.length; i++) {
promises.push(createorUpdateMarketAsync(res.response.result[i], eventObj));
}
Promise.all(promises).then(function(marketArray) {
// all results done here, results in marketArray
}, function(err) {
// an error occurred
});
Enhanced Promises with the Bluebird Promise library
The bluebird promise library offers Promise.map() which will iterate over your array of data and produce an array of asynchronously obtained results.
// make a version of createOrUpdateMarket that returns a promise
var Promise = require('bluebird');
var createOrUpdateMarketAsync = Promise.promisify(createOrUpdateMarket);
// iterate the res.response.result array and run an operation on each item
Promise.map(res.response.result, function(item) {
return createOrUpdateMarketAsync(item, eventObj);
}).then(function(marketArray) {
// all results done here, results in marketArray
}, function(err) {
// an error occurred
});
Async Library
You can also use the async library to help manage multiple async operations. In this case, you can use async.map() which will create an array of results.
var async = require('async');
async.map(res.response.result, function(item, done) {
createOrUpdateMarker(item, eventObj, function(err, marketObj) {
if (err) {
done(err);
} else {
done(marketObj);
}
});
}, function(err, results) {
if (err) {
// an error occurred
} else {
// results array contains all the async results
}
});

Q.allSettled executes before anything is pushed to the array

I'm trying to learn how to use promises with arrays and some async mongo queries. Here's the method that I currently have, but Q.allSettled executes before my mongo queries b/c nothing has been pushed to the array yet that Q.allSettled is looking at.
How can I modify this method so that all my async queries are executed before Q.allSettled.spread executes?
function buildCaseObject() {
var returnPromise = Q.defer();
var promises = [];
var allObjects = [];
var subjects = rdb.collection('subjects');
var observations = rdb.collection('observation');
// Loop through all subjects from current subject list
subjects.find({'player._id': {$elemMatch: {root: '1.2.3.99.100.2', extension: {$in : subjectList}}}}).each(function(err, subject) {
var def = Q.defer();
promises.push(def);
if (err) {
def.reject(err);
} else if (subject== null) {
return def.resolve();
}
var caseObject = {};
caseObject.subject= clone(subject);
// Add observations to the subject
observations.find({subjectId: subject._id}).toArray(function(err, allObs) {
if (err) {
def.reject(err);
}
caseObject.observations = clone(allObs);
allObjects.push(caseObject);
def.resolve();
});
});
Q.allSettled(promises).then(function() {
// GETTING IN HERE BEFORE GETTING INTO THE CALLBACK OF subjects.find.
// THEREFORE THE ARRAY IS EMPTY
console.log('in spread');
console.log(allObjects.length);
returnPromise.resolve(allObjects);
}).fail(function(err) {
returnPromise.reject(err);
});
return returnPromise.promise;
}
Two things:
Q.allSettled will only capture the promises that are in the array at the time it is called.
You will need to wait until you have populated the array, perhaps with a promise for the completion of the each call above.
The other is that Q.defer() returns a {promise, resolve} pair. You will need to add only the promise to the promises array.
promises.push(def.promise);

Categories