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.
Related
I'm new to NodeJS and Jade/PUG so I know this can be a really simple question for many of you but for me I can't understeand any of the answers a get from the internet because the term 'Promise' and how it works is a little "confusing" for me.
I am querying a postgre database to get several values from a table (really simple query). If I do this without using a promise everything works fine, console prints the result and everyone is happy, but when I try to store this result into a variable and pass it as a parameter to a Jade template things change.
I have read that, in order to do that, I need to use promises because it is more likely that when the variable is being accessed, the value might not be resolved yet and that's what Promises are for. So here I have my code:
hero.js:
getHeroes: function()
{
//Initialize array
var elem = [];
console.log('Querying heroes');
return new Promise((resolve, reject) =>
{
pg.connect(conString, function (err, client, done)
{
if (err)
{
return console.error('error fetching client from pool', err)
}
//Execute SELECT query
client.query('SELECT name from heroe;', function (err, rows, result)
{
//Iterate over results
for (var i = 0; i < rows.rowCount; i++)
{
//PUSH result into arrays
elem.push(rows.rows[i].name);
}
done()
if (err)
{
return console.error('error happened during query', err)
}
resolve(elem)
})
});
})
}
And this the part of my server.js where I call that function:
app.get('/jade', (request, response) => {
var heroList = [];
heroList = hero.getHeroes();
console.log(heroList);
response.render('test_jade', {param1: 'test'});
})
that console.log shows up "Promise { pending }" and I don't know how to "listen to the resolved event and retrieve the value from it once it has finished".
Would appreciate any advice/solution or even a good Node.js manual where all this mess is explained for total newbies like me.
Thanks in advance!
It's not how you use promise.
Try this,
app.get('/jade', (request, response) => {
var heroList = [];
hero.getHeroes().then(data=>{
heroList = data;
console.log(heroList);
response.render('test_jade', {param1: 'test'});
}).catch(e=>{
//handle error case here when your promise fails
console.log(e)
})
})
You should also catch in case your promise fails.
Edit: I can't believe I forgot what I was asking! I always get typeError Undefined for whatever the sixth if statement is testing. When I swap around the fifth/sixth statements the if(!data.body.main) comes out as undefined which it doesn't when in the fifth position however I can't figure out why.
So I'm trying to test POST data from express/bodyparser for its contents and then fill default information to be used by the rest of my program if the fields aren't set in the post data.
To do this I'm running many if statements in a list like so:
function setChannelOptions(data, channelSettings){
console.log('Setting channel options');
var channelOptions = {
channelName: data.body.name,
mainNumber: data.body.main,
backupNumber: data.body.backup,
music: data.body.music,
spoofNumber: data.body.spoof,
price: data.body.price,
customerId: data.body.customerid,
allowOutbound: data.body.allowoutbound,
welcomeMessage: data.body.welcomemessage,
accountCode: data.body.accountCode
}
if(!data.body.accountCode){
channelOptions.accountCode = findChannelID(data.body.name, function (err, data) {
if(err) throw err;
return data.channelid;
});
}
if(!data.body.allowoutbound){
channelOptions.allowOutbound = 0;
}
if(!data.body.welcomemessage){
channelOptions.welcomeMessage = 'sometext';
}
if(!data.body.price){
channelOptions.price = '-1';
}
if(!data.body.backup){
var data = 'NG';
channelOptions.backupNumber = assignNumber(data, function(err, data){
if (err) throw err;
return data.number;
})
}
if(!data.body.main){
var data = 'VU';
channelOptions.mainNumber = assignNumber(data, function(err, data){
if (err) throw err;
return data.number;
})
}
channelSettings(null, channelOptions);
I feel like it might be caused by the callback at the end returning before all of the if statements are complete but without nesting each of these if's in callback functions I wouldn't know how to stop that.
Thanks for having a look, I'm a bit new to node and JS in general.
Try using other name for your variables. You use data identifier as incoming parameter of your setChannelOptions function, and then redefine it with var keyword. Variables defined with var are functionally, not block-scoped. Refactor your code to something like:
if(!data.body.backup){
var code = 'NG';
channelOptions.backupNumber = assignNumber(code, function(err, data){
if (err) throw err;
return data.number;
})
}
if(!data.body.main){
var code = 'VU';
channelOptions.mainNumber = assignNumber(code, function(err, data){
if (err) throw err;
return data.number;
})
}
Basically you redefined your data arg. You should probably copy data at the top of your function and use that copy instead.
if(!data.body.backup){
var data = 'NG'; // <----- redefinition
channelOptions.backupNumber = assignNumber(data, function(err, data){
if (err) throw err;
return data.number;
})
}
if(!data.body.main){ // data === 'NG'
var data = 'VU';
channelOptions.mainNumber = assignNumber(data, function(err, data){
if (err) throw err;
return data.number;
})
}
Use promise library like bluebird.
require a promise library
var Promise = require('bluebird'); // install it using nom
Declare a array of promises as
var promises = [];
if(!data.body.accountCode){
promises.push(
findChannelID(data.body.name).then(function (data){
channelOptions.accountCode = data.channelid;
})
);
}
//.....rest all other checks
Promise.all(promises).then(function(){
channelSettings(null, channelOptions);
});
you will need to convert your callback function (like findChannelID) to return a Promise. which is very simple as
function findChannelID(name){
return new Promise(function(resolve, reject){
//.. do you math here
// finally return value as
// resolve(the calculated value);
resolve({data:{name: 'something'}})
});
}
I have a requirement where I need to get the records from table 1 and store in redis cache and once redis cache is finished storing, get table 2 records and store in redis cache. So there are 4 asynchronous functions.
Steps:
Get table 1 records
Store in redis cache
Get table 2 records
Store in redis cache
What is the correct procedure to handle it.
Below is the code which I have written to handle it. Please confirm whether its the right procedure or any other ways to handle it as per node.js
var redis = require("redis");
var client = redis.createClient(6379, 'path', {
auth_pass: 'key'
});
var mysqlConnection = // get the connection from MySQL database
get_Sections1()
function get_Sections1() {
var sql = "select *from employee";
mysqlConnection.query(sql, function (error, results) {
if (error) {
console.log("Error while Sections 1 : " + error);
} else {
client.set("settings1", JSON.stringify(summaryResult), function (err, reply){
if (err) {
console.log("Error during Update of Election : " + err);
} else {
get_Sections2();
}
});
}
});
}
function get_Sections2()
{
var sql = "select *from student";
mysqlConnection.query(sql, function (error, results)
{
if (error)
{
console.log("Error while Sections 2 : " + error);
}
else
{
client.set("settings2", JSON.stringify(summaryResult), function (err, reply)
{
if (err)
{
console.log("Error during Update of Election : " + err);
}
else
{
console.log("Finished the task...");
}
});
}
});
}
Create two parameterised functions. One for retrieval, one for storing.
Then promisify them both.
Then write:
return getTableRecords(1)
.then(storeInRedisCache)
.then(getTableRecords.bind(null,2))
.then(storeInRedisCache)
.then(done);
To promisify a function, something like this might work:
var getEmployees = new Promise(function(resolve, reject) {
var sql = "select *from employee";
mysqlConnection.query(sql, function (error, results) {
if (error) {
return reject();
} else {
return resolve(results);
}
});
});
If you are using an old version of NodeJS you will need a polyfill for Promise.
Here is an alternative to Ben Aston's solution using Promise.coroutine assuming promises:
const doStuff = Promise.coroutine(function*(){
const records = yield getTableRecords(1);
yield storeRecordsInCache(records);
const otherRecords = yield getTableRecords(2);
yield storeRecordsInCache(otherRecords); // you can use loops here too, and try/cath
});
doStuff(); // do all the above, assumes promisification
Alternatively, if you want to use syntax not yet supposed in Node (and use Babel to get support) you can do:
async function doStuff(){
const records = await getTableRecords(1);
await storeRecordsInCache(records);
const otherRecords = await getTableRecords(2);
await storeRecordsInCache(otherRecords); // you can use loops here too, and try/cath
})
I have a Node.js function to fetch some value from DB table
var GetPoints = function(ibmdb, dbConnection, msisdn) {
ibmdb.open(dbConnection, function(err, conn) {
if (err) {
//Error
} else {
conn.query('SELECT id,msisdn,points FROM t_points WHERE msisdn =' + msisdn, function(err, data) {
console.log(err);
if (!err) {
conn.close(function(err) {
if (!err) {}
});
consele.log(data);
//return data[0].POINTS;
} else {
//Error
}
});
}
console.log("points" + points);
});
}
I want to know how I can access the data object when I call this function from outside
var data = GetPoints(ibmdb, dbConnection, msisdn);
The value is coming correctly when I do a console.log
You can't return the value from an async function directly. Promises are generally used this situation. You return a promise which can later be called .then upon to retrieve the said value.
var Promise = require('bluebird');
var GetPoints = function(ibmdb, dbConnection, msisdn) {
// return a Promise
return new Promise(function(resolve){
ibmdb.open(dbConnection, function(err, conn) {
if(err) throw err; // throw the error for it to be caught
…
conn.query('SELECT …', function(err, data) {
if(err) throw err;
…
consele.log(data);
//return data[0].POINTS;
resolve(data);
}); }); }); }
GetPoints().then(function(data){
// do something with data
}).catch(function(err){
// handle err
});
Additionally, Bluebird has a promisify function that turns an async function (that takes a callback) into a function that returns a Promise. It makes the above code much simpler:
Note: Although I was reluctant because if you're using MySQL with which the promisification could be a little tricky: 1, 2. But For now I've added .promisifyAll where it might seem redundant as it will likely be executed more than once, but hopefully bluebird's promisification is smart enough to handle this. Nonetheless if you manage to promisify more efficiently you can just remove the redundant promisifyAll and just use X.yyyAsync methods as described:
function GetConnection(ibmdb, dbConnection, msisdn){
Promise.promisifyAll(ibmdb);
return ibmdb.openAsync();
}
function getData(conn){
Promise.promisifyAll(conn);
return conn.queryAsync('SELECT …');
}
GetConnection()
.then(getData)
.then(function(data){
// do something with data
})
The callback function you gave after the SQL query gets executed asynchronously. Instead of trying to get that data outside the function, you should try to perform whatever you need to do inside. Keep in mind you can create another function and call it with the data to continue your work.
i have a little problem whit translation of a PHP sync code to Node.
PHP
$bodies = getBodystyles();
foreach($bodies as $b) {
$aCar['body'] = $b['Body'];
$code = getCode($aCar);
$promo = checkPromotionByCarID($code['Car']);
}
i'll try to convert it in this way:
NODE
db.getBodystyles(function(err, bodies){
for (b in bodies) {
console.log(bodies[b]);
var aCar = Common.ConfiguratorDefault;
db.getCode(aCar, function(err, code){
console.log(code);
bodies[b].aCar = aCar;
bodies[b].code = code;
// checkPromotionByCarID here?
});
}
options.bodies = bodies;
showView(options);
});
but with this code showView run before getCode calling finish.
what the right approach to do that?
any help would be appreciated
UPDATE
I see this question is marked as duplicated.
The suggested duplicate are here
However the question proposed doesn't solve my problem.
I need to perform a function (showView) after loop finish and after all asynchronous calls inside the loop are finished.
i start from a Select on DB and for each element of result i need to perform other two queries.
Only after that queries are finished i need to perform my function and i doesn't understand how do that, and the question suggested doesn't solve my need.
as suggested, the solution to my problem is obtained with the libraries Async or Rsvp.
Below are two possible solutions:
ASYNC
Using Async.js
db.getBodystyles(function(err, bodies){
async.each(bodies, function( b, callback) {
var aCar = Common.ConfiguratorDefault;
aCar.body = b.Body;
db.getCode(aCar, function(err, code){
if (err){
callback(err);
}else{
b.aCar = aCar;
b.code = code;
db.checkPromotionByCarID(code.Car, function(err, promo){
if (err){
callback(err);
}else{
b.promo = promo;
callback();
}
});
}
});
},function(err){
if( err ) {
console.log('A file failed to process');
res.render('500');
} else {
console.log('All files have been processed successfully');
options.bodies = bodies;
showView(options);
}
});
});
RSVP
Using Rsvp.js
db.getBodystyles(function(err, bodies){
var promises = bodies.map(function(b) {
var aCar = Common.ConfiguratorDefault;
aCar.body = b.Body;
var promise = new RSVP.Promise(function(resolve, reject) {
db.getCode(aCar, function(err, code){
if(err) reject(err);
b.aCar = aCar;
b.code = code;
db.checkPromotionByCarID(code.Car, function(err, promo){
if(err) reject(err);
b.promo = promo;
resolve();
});
});
});
return promise;
};
RSVP.all(promises).then(function() {
options.bodies = bodies;
showView(options);
}).catch(function(err) {
throw err;
});
});