node.js callback inside mysql result loop - node.js

I am trying to get my head around this non-blocking business.
Can you tell me what i am doing wrong here? I have tried to add a retrieveContactName function which gets more information from the db before writing to socket. I have tried to make it a callback but i get the error at bottom.
case 'getConversations':
var sip = parts[1];
var pass = parts[2].replace(/[\n\r]/g, ''); //strip that carriage return
var sql = "SELECT DISTINCT(session) FROM im WHERE sip = "+connection.escape(sip)+" AND password = "+connection.escape(pass)+" ORDER BY timestamp DESC";
connection.query(sql, function(err, results) {
if (err) winston.warn(err);
for (var i=0; i < results.length; i++) {
retrieveContactName(results[i].session, sip, pass, function(value) {
sock.write('Conversations '+results[i].session+' '+value+'\n');
});
}
});
break;
elsewhere
function retrieveContactName(session, user, pass, callback) {
var sql = "SELECT `im_from` FROM `im` WHERE `session` = "+connection.escape(session)+" AND `im_to` != "+connection.escape(session)+" AND `sip` = "+connection.escape(user)+" AND `password` = "+connection.escape(pass)+" LIMIT 1";
connection.query(sql, function(err, results) {
if (err) winston.warn(err);
if (results.length > 0 ) {
callback(results[0].im_from);
} else {
callback(session.replace("contact:",""));
}
});
}
and my error
TypeError: Cannot read property 'session' of undefined
at /home/ubuntu/socket/server.js:44:47
at Query._callback (/home/ubuntu/socket/server.js:79:4)
at Query.end (/home/ubuntu/socket/node_modules/mysql/lib/protocol/sequences/Sequence.js:75:24)
at Query._handleFinalResultPacket (/home/ubuntu/socket/node_modules/mysql/lib/protocol/sequences/Query.js:143:8)
at Query.EofPacket (/home/ubuntu/socket/node_modules/mysql/lib/protocol/sequences/Query.js:127:8)
at Protocol._parsePacket (/home/ubuntu/socket/node_modules/mysql/lib/protocol/Protocol.js:172:24)
at Parser.write (/home/ubuntu/socket/node_modules/mysql/lib/protocol/Parser.js:62:12)
at Protocol.write (/home/ubuntu/socket/node_modules/mysql/lib/protocol/Protocol.js:37:16)
at Socket.ondata (stream.js:38:26)
at Socket.emit (events.js:88:20)

Your problem is down to not fully understanding scope, basically the issue is occurring here:
for (var i=0; i < results.length; i++) {
retrieveContactName(results[i].session, sip, pass, function(value) {
sock.write('Conversations '+results[i].session+' '+value+'\n');
});
}
The above will not work as you expect, all because by the time your callback fires i will not have the value you expect... it will most likely be equal to results.length which will give you an undefined slot in results. This is because the for will most likely have continued on and finished it's execution before the callbacks ever occur. This is the principal of non-blocking, code does not wait, it carries on, and your callbacks have to be prepared for this.
In order to use the value of i, or any variable that may change it's value outside the scope of your callback, you need to capture that value and store it with your callback. There are a few ways to do this but the nicest way is to pass the data your callback requires in as arguments — so you need to pass the results[i].session (passed into retrieveContactName) on to your callback.
function retrieveContactName(session, user, pass, callback) {
var sql = "SELECT `im_from` FROM `im` WHERE `session` = "+connection.escape(session)+" AND `im_to` != "+connection.escape(session)+" AND `sip` = "+connection.escape(user)+" AND `password` = "+connection.escape(pass)+" LIMIT 1";
connection.query(sql, function(err, results) {
if (err) winston.warn(err);
if (results.length > 0 ) {
callback(results[0].im_from, session);
} else {
callback(session.replace("contact:",""), session);
}
});
}
And then:
for (var i=0; i < results.length; i++) {
retrieveContactName(results[i].session, sip, pass, function(value, session) {
sock.write('Conversations '+session+' '+value+'\n');
});
}
Obviously this is just a quick example, it may be best to pass in the actual result row — depending on what you want / need.

results[i] may be out of scope inside the callback
case 'getConversations':
var sip = parts[1];
var pass = parts[2].replace(/[\n\r]/g, ''); //strip that carriage return
var sql = "SELECT DISTINCT(session) FROM im WHERE sip = "+connection.escape(sip)+" AND password = "+connection.escape(pass)+" ORDER BY timestamp DESC";
connection.query(sql, function(err, results) {
if (err) winston.warn(err);
for (var i=0; i < results.length; i++) {
retrieveContactName(results[i].session, sip, pass, function(value) {
sock.write('Conversations '+results[i].session+' '+value+'\n');
});
}
});
break;
can you place a console.log(results) just above:
sock.write('Conversations '+results[i].session+' '+value+'\n');

Related

How can I execute a query based on the result of another query? (Express, NodeJS, mysql)

I am trying to generate a session ID when an API call is made to my 'generateSession' endpoint. I want to make sure that I don't have any duplicate session ID, so I query the database checking for a match on the generated ID. If there isn't a match, the ID is valid and I make a second query to add an active user with said session ID.
Although my first query executes, the 'id_is_valid' boolean never gets set to true so my program gets stuck in the while loop.
I am fairly new to JavaScript, but from some research, I'm pretty sure the problem is due to the asynchronous nature of the database call. However, I'm not sure where to go from there. Could anyone with more js knowledge offer me some direction?
Thanks!
var express = require('express');
var router = express.Router();
var myDB = require('../db-connection');
function generateSession() {
var session_id = '';
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 30; i++) session_id += possible.charAt(Math.floor(Math.random() * possible.length));
return session_id;
}
router.get('/generateSession', function(req, res){
var session_id = '';
var id_is_valid = false;
while (!id_is_valid){
session_id = generateSession();
myDB.query("SELECT * FROM activeUser WHERE session_id = ?", [session_id], function(error, results, field){
if(error) throw error;
else{
if (results.length === 0) is_is_valid = true;
}
});
}
myDB.query("INSERT INTO activeUser (is_registered, session_id) VALUES (0, ?)", [session_id], function(error, results, fields){
if (error) res.send('{"success": false}');
else res.send('{"success": true, "session_id": "' + session_id + '"}');
});
});
Although my first query executes, the id_is_valid boolean never gets set to true so my program gets stuck in the while loop.
This is because db call, by nature is asynchronous. If you run the following program you will know it.
'use strict';
let id_is_valid = false;
let count = 0;
while (!id_is_valid) {
count++;
console.log(`No of time setTimeout Invoked ${count}`);
setTimeout(function() { // simulating a db call that takes a second for execution
id_is_valid = true;
}, 1000);
}
console.log('This line wont be printed');
Outputs
No of time setTimeout Invoked 61415
No of time setTimeout Invoked 61416
No of time setTimeout Invoked 61417
^C //I killed it.
Like damitj07, I too suggest using npms like shortId for uniquely generating the sessionId. This will help you to eliminate a database call.
But if your business logic restricts and you need it in the current fashion you wrote. I think we can use async & await
'use strict';
let isFound = false;
let count = 0;
function doDbQuery() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve(true);
}, 2000);
});
}
async function run() {
while (!isFound) {
count++;
console.log(`No of time setTimeout Invoked ${count}`);
isFound = await doDbQuery();
}
console.log('This line WILL BE printed');
}
run();
Output
No of time setTimeout Invoked 1
This line WILL BE printed
Making those changes to your code,
router.get('/generateSession', async function (req, res) {
var session_id = '';
var id_is_valid = false;
while (!id_is_valid) {
session_id = generateSession();
id_is_valid = await checkSessionIdInDb(session_id);
}
myDB.query('INSERT INTO activeUser (is_registered, session_id) VALUES (0, ?)', [session_id], function (error, results, fields) {
if (error) {
res.send('{"success": false}');
} else {
res.send('{"success": true, "session_id": "' + session_id + '"}');
}
});
});
function checkSessionIdInDb() {
return new Promise((resolve, reject) => {
myDB.query('SELECT * FROM activeUser WHERE session_id = ?', [session_id], function (error, results, field) {
if (error) {
return reject(error);
} else {
if (results.length === 0) {
resolve(true);
}
resolve(false);
}
});
});
From what I could make out of your code what you want to do is basically create a new user session for logged in user with a unique session Id, also making sure that the session ID does not already exist in the collection.
So the solution to this can be to the first query to check if the session ID is already present in Active_Users Collection, if not make a save call to save the user with generated session Id.
var express = require('express');
var router = express.Router();
var myDB = require('../db-connection');
function generateSession() {
var session_id = '';
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 30; i++) session_id += possible.charAt(Math.floor(Math.random() * possible.length));
return session_id;
}
router.get('/generateSession', function(req, res) {
var session_id = '';
myDB.query("SELECT * FROM activeUser WHERE session_id = ?", [session_id], function(error, results, field) {
if (error) {
throw error;
} else {
if (results.length === 0) {
//if session id is not present , insert new user
session_id = generateSession();
myDB.query("INSERT INTO activeUser (is_registered, session_id) VALUES (0, ?)", [session_id], function(error, results, fields) {
if (error) res.send('{"success": false}');
else res.send('{"success": true, "session_id": "' + session_id + '"}');
});
}
//else do nothing or inser nothing
}
});
});
But Ideally, if you are generating a truly random session id each time when you make a request to the /generate session, is it really necessary to check for duplicates in the collection.
Now if the logic to generate a random ID is not perfect, you can always use modules like shortId to do the work for you. This will avoid unnecessary database call and save on performance and your code will be much neater.

callback is not a function node js

I am new to javascript and i am having trouble solving this error. I get the message: "callback is not a function" at:"return callback(rolesArray)".
Rol.getAllRoles = function(callback){
sql = "select role from Role;";
var rolesArray = [];
var role;
mysql.connection(function(err,conn){
if (err){
return callback(err);
}
conn.query(sql,function(err,rows){
if (err){
return callback(err);
}
for(var i=0; i < rows.length; i++){
role = rows[i].role;
rolesArray.push(rol);
}
console.log("roles: " + rolesArray);
return callback(rolesArray);
});
});
}
The console.log outputs:"roles: admin,customer" so the connection with the database works.
That error means that you are not passing a function to Rol.getAllRoles(fn) when you call it.
In addition, so that you can have proper error handling in your callback and so you can more easily distinguish between an error and the actual data, you should always pass a first argument to the callback that indicates whether there was an error or not and then the second argument (if not an error) can be your results array like this:
Rol.getAllRoles = function(callback){
sql = "select role from Role;";
var rolesArray = [];
var role;
mysql.connection(function(err,conn){
if (err){
return callback(err);
}
conn.query(sql,function(err,rows){
if (err){
return callback(err);
}
for(var i=0; i < rows.length; i++){
role = rows[i].role;
rolesArray.push(rol);
}
console.log("roles: " + rolesArray);
// make sure the first argument to the callback
// is an error value, null if no error
return callback(null, rolesArray);
});
});
}
And, then you should be calling it like this:
Rol.getAllRoles(function(err, rolesArray) {
if (err) {
// handle error here
} else {
// process rolesArray here
}
});
This style of calling an async callback as in callback(err, data) is a very common async callback design pattern. It allows all callers to see if there was an error or not and if there was no error to get access to the final result.
I'd suggest the following:
Rol.getAllRoles = function(callback){
var sql = "select role from Role;";
var rolesArray = [];
var role;
callback = callback || function(){};
mysql.connection(function(err,conn){
if (err){
return callback(err);
}
conn.query(sql,function(err,rows){
if (err){
return callback(err);
}
for(var i=0; i < rows.length; i++){
role = rows[i].role;
rolesArray.push(rol);
}
console.log("roles: " + rolesArray);
return callback(rolesArray);
});
});
}
This way you enforce that callback is always a function. If you run it like Rol.getAllRoles() then you would get an error previously. Now you wont. You wont get any data back though.
Make sure you are calling Rol.getAllRoles with the proper parameter (ie: a function).

Node.js + socket.io + MySQL correction of syntax

Considering that my server.js looks almost like this. Just send you the relevant part. I did not receive anything from the query, I do have data in the database, and "sendNotification" is triggered by the jQuery function in the client. Everything works and since var notis = []; returns an empty value and is what is shows as response. I know I have to debug SQL and that's what I'm going to do but anyway want to be sure of this other things. So my questions are:
1) Is a right syntax for node.js, considering this async behavior? (which I still don't understand )
2) The query always should be inside of the "io.sockets.on('connection')" part?
connection = mysql.createConnection({
host: 'localhost',
user: '',
password: "",
database: 'table' //put your database name
}),
...
connection.connect(function(err) {
// connected! (unless `err` is set)
console.log(err);
});
…
var sqlquery = function(uID,vs){
var notis = [];
connection.query("SELECT * FROM notification WHERE kid = ? AND v = ? ORDER BY id DESC",[uID,vs])
.on("result", function (data){
return notis.push(data);
});
};
io.sockets.on('connection', function(socket) {
...
socket.on("sendNotification", function(data) {
var roomBName = data.room_name.replace("room-",""),
found = [];
var roomSelected = _.find(rooms, function (room) { return room.id == roomBName });
for (var person in people) {
for (var i = 0, numAttending = roomSelected.peopleAttending.length; i < numAttending; i++) {
if (people[person].name == roomSelected.peopleAttending[i]) {
found.push(person);
}
}
}
for (var i = 0, numFound = found.length; i < numFound; i++) {
**result = sqlquery(9,2);**
io.to(found[i]).emit('notification', result);
};
});
Your sqlquery() function will not accomplish anything useful. Because connection.query() is asynchronous, that means it provides the response sometime LATER after sqlquery() has already finished.
The only way in node.js to use an async result is to actually use it in the callback that provides it. You don't just stuff it into some other variable and expect the result to be there for you in other code. Instead, you use it inside that callback or you call some other function from the callback and pass it the data.
Here's one way, you could change your sqlquery() function:
var sqlquery = function(uID, vs, callback){
connection.query("SELECT * FROM notification WHERE kid = ? AND v = ? ORDER BY id DESC",[uID,vs])
.on("result", function (data){
callback(null, data);
});
// need to add error handling here if the query returns an error
// by calling callback(err)
};
Then, you could use the sqlquery function like this:
found.forEach(function(person, index) {
sqlquery(..., function(err, result) {
if (err) {
// handle an error here
} else {
io.to(person).emit('notification', result);
}
});
});
And, it looks like you probably have similar async issues in other places too like in connection.connect().
In addition to #jfriend00, this could be done with new ES6 feature Promise :
var sqlquery = function(uID, vs){
return new Promise(function(resolve, reject){
connection.query("SELECT * FROM notification WHERE kid = ? AND v = ? ORDER BY id DESC",[uID,vs])
.on("result", function (data){
resolve(data);
});
});
};
Now you can use it like :
found.forEach(function(person, index) {
sqlquery(...)
.then(function(result){
io.to(person).emit('notification', result);
});
});

synchronous loop in nodejs

//Importing: postges DB connection
var pg = require('pg');
var conString = "postgres://readxxx:p#ssword#vmwoxxx-tst:8888/worxxx";
var prvsiteid = '';
var cursiteid = '';
var qurystring = '';
pg.connect(conString, function(err, client, done) {
if (err) {
return console.error('error fetching client from pool', err);
return;
}
client.query("select site_id,created_at,started_at,completed_at,notes,finish_code from _background_tasks where finish_code > 0 and site_id > 0 and abs(extract(Epoch from (now()::timestamp without time zone - completed_at)))/60 <= 4600 order by site_id asc", function(err, result1) {
done();
if (err) {
return console.error('error running query', err);
return;
}
for (var i = 0; i < result1.rowCount; i++) {
cursiteid = result1.rows[i].site_id;
if (prvsiteid != cursiteid) {
prvsiteid = cursiteid;
qurystring = "select trim(su.name) as name, case When trim(su.email) is null then su.name || '#netapp.com' when trim(su.email) > '' then su.name || '#netapp.com' else su.name end uemail,su.friendly_name as frdname from system_users su where su.state = 'active' and su.id in (select distinct(system_user_id) from users u where u.site_id = " + cursiteid + "and u.admin_level >= 5)"
client.query(qurystring, function(err, result2) {
done();
if (err) {
return console.error('error running query', err);
return;
}
for (var j = 0; j < result2.rowCount; j++) {
console.log(cursiteid, result2.rows[j].name, result2.rows[j].uemail, result2.rows[j].frdname);
}
});
}
console.log(result1.rows[i].site_id, result1.rows[i].created_at, result1.rows[i].started_at, result1.rows[i].completed_at);
}
});
});
I know NodeJS programs are asynchronous but this scenario I intend it to be synchronous.
for loop(outer) --> for loop(inner) when outer forloop changes with new site id i want to send email to all the emailids from inner loop and also the resultant rows of the each site of outer loop has to printed.
If the library you are using doesn't support synchronous operations, you can't use it synchronously without abominations (You do not want abominations in your code. No matter how much the boss is pressing).
You can fairly simply perform loops and other actions on asynchronous operations by using Promises, specifically, bluebird which supports the ability to take callback-based asynchronous functions and transform them into Promise-ready functions.

Synchronous for loop in node js

So let's say I have the following for loop
for(var i = 0; i < array.length; i++){
Model.findOne({ _id = array[i].id}, function(err, found){
//Some stuff
});
}
How do I make this code work? Every time I run it I get array[i] = undefinedbecause the mongo-db query is asynchronous and the loop has already iterated 5 times by the time the first query is even completed. How do I go about tackling this issue and waiting for the query to complete before going on to the next iteration?
This doesn't specifically answer your question, but addresses your problem.
I'd use an $in query and do the filtering all at once. 20 calls to the db is pretty slow compared to 1:
// grab your ids
var arrayIds = myArray.map(function(item) {
return item._id;
});
// find all of them
Model.find({_id: {$in: arrayIds}}, function(error, foundItems) {
if (error) {
// error handle
}
// set up a map of the found ids
var foundItemsMap = {};
foundItems.forEach(function(item) {
foundItemsMap[item._id] = true;
});
// pull out your items that haven't been created yet
var newItems = [];
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
if ( foundItemsMap[arrayItem._id] ) {
// this array item exists in the map of foundIds
// so the item already exists in the database
}
else {
// it doesn't exist, push it into the new array
newItems.push(arrayItem);
}
}
// now you have `newItems`, an array of objects that aren't in the database
});
One of the easiest ways to accomplish something like you want is using promises. You could use the library q to do this:
var Q = require('q');
function fetchOne(id) {
var deferred = Q.defer();
Model.findOne({ _id = id}, function(err, found){
if(err) deferred.reject(err);
else deferred.resolve(found);
});
return deferred.promise;
}
function fetch(ids, action) {
if(ids.length === 0) return;
var id = ids.pop();
fetchOne(id).then(function(model) {
action(model);
fetch(ids, action);
});
}
fetch([1,2,3,4,5], function(model) { /* do something */ });
It is not the most beautiful implementation, but I'm sure you get the picture :)
Not sure if this is the right way, it could be a bit expensive but this how i did it.
I think the trick is to pull all your data and then looking for an id match.
Model.find(function(err, data) {
if (err) //handle it
for (var i=0; i<array.length; i++) {
for (var j=0; ij<data.length; j++) {
if(data[j].id == array[i].id) {
// do something
}
}
}
}

Resources