Scope issue calling a callback function inside a loop in node.js - javascript

With the help of glortho in this thread i built this code:
for(var i=0;i<datos.length;i++){
bittrex.getticker(datos[i].Currency, function(err, data){
if (err){
console.log('ERROR:', err);
return 'ERROR:'+ err;
} else {
if (data.message!='INVALID_MARKET') {
this.LasValueBTC=data.result.Last;
} else {
this.LasValueBTC='';
}
}
}.bind(datos[i]));
}
The problem is that outside the callback function the datos array is not updated...As it is written at the moment if i console.log(this) inside the function works great and this.LastValueBTC exists in my json, but outside the function if i console.log(datos) after the loop, the LastValueBTC does not exist..and i need to do a res.send(datos) after the loop..

What you need to do is wait for all the callbacks to complete and then call res.send.
var count = datos.length;
for(var i=0;i<datos.length;i++){
bittrex.getticker(datos[i].Currency, function(err, data){
if (err){
console.log('ERROR:', err);
return 'ERROR:'+ err;
} else {
if (data.message!='INVALID_MARKET') {
this.LasValueBTC=data.result.Last;
} else {
this.LasValueBTC='';
}
count--;
if (count === 0) {
res.send(datos);
}
}
}.bind(datos[i]));
}
Or using async
async.each(datos, function(dato, next) {
bittrex.getticker(dato.Currency, function(err, data) {
if (err){
console.log('ERROR:', err);
next(err);
} else {
if (data.message!='INVALID_MARKET') {
dato.LasValueBTC = data.result.Last;
} else {
dato.LasValueBTC='';
}
next();
}
});
}, function(err) {
res.send(datos);
});

Go through this article http://www.richardrodger.com/2011/04/21/node-js-how-to-write-a-for-loop-with-callbacks/#.VTXnFa2eDGc
It gives a good conceptual overview on what happens if you put functions inside for loop

Related

SQlite result undefined issue in NodeJS [duplicate]

How do I get the return value from inside a value of node.js/javascript callback?
function get_logs(){
User_Log.findOne({userId:req.user._id}, function(err, userlogs){
if(err) throw err;
if(userlogs){
// logs = userlogs.logs;
return "hello there is a logs";
} else {
return "there is no logs yet..."
}
})
}
var logs = get_logs();
console.log(logs);
You can't return the result from a function whose execution is asynchronous.
The simplest solution is to pass a callback :
function get_logs(cb){
User_Log.findOne({userId:req.user._id}, function(err, userlogs){
if(err) throw err;
if(userlogs){
// logs = userlogs.logs;
cb("hello there is a logs");
} else {
cb("there is no logs yet...)"
}
})
}
get_logs(function(logs){
console.log(logs);
});
You can't. You should instead pass another callback to your function. Something like this:
function get_logs(callback){
User_Log.findOne({userId:req.user._id}, function(err, userlogs){
if(err) throw err;
if(userlogs){
callback("hello there is a logs");
} else {
callback("there is no logs yet...");
}
})
}
get_logs(function(arg1) {
console.log(arg1);
});
function get_logs(callback) {
User_Log.findOne({
userId: req.user._id
}, function (err, userlogs) {
if (err) throw err;
if (userlogs) {
// logs = userlogs.logs;
callback("hello there is a logs");
} else {
callback("there is no logs yet...");
}
})
}
get_logs(function (data) {
console.log(data);
});
Uses callbacks...
In node.js almost all the callbacks run after the function returns , so you can do something like this
function get_logs(){
User_Log.findOne({userId:req.user._id}, function(err, userlogs){
if(err) throw err;
if(userlogs){
// logs = userlogs.logs;
do_something(logs)
} else {
console.log('No logs')
}
})
}

Node.js async iteratee with timeout

Bit new to node.js and javascript. Attempting to have my foreach call a function that in turn does a remote call. I'd like there to be a delay between each but can't seem to work out where to put the set timeout.
I know I have my setTimeout in the wrong place below but putting it in there as an example.
var likeRecommendation = function (recommendation, callback) {
context.Client.like(recommendation._id, function (error, data) {
recommendation['drupal_user_uid'] = context.message.uid;
recommendation['drupal_user_uuid'] = context.message.uuid;
if (error) return callback(new Error('Could not like recommendations'));
context.broker.publish('saves_swipes_publication', recommendation, function (err, publication) {
if (err) return callback(new Error('Could queue swipes to save'));
publication.on('error', console.error);
});
console.log('Liked!');
return callback()
});
}
async.forEach(context.recommendations, likeRecommendation, function (error) {
if (!error) return done(null);
done(new Error('Could not like recommendations'));
});
}
See timeout, warning one of your callback was missing and one other was not well placed.
var likeRecommendation = function (recommendation, callback) {
context.Client.like(recommendation._id, function (error, data) {
recommendation['drupal_user_uid'] = context.message.uid;
recommendation['drupal_user_uuid'] = context.message.uuid;
if (error)
setTimeout(function(){return callback(new Error('Could not like recommendations'))}, 100);
else {
context.broker.publish('saves_swipes_publication', recommendation, function (err, publication) {
if (err)
setTimeout(function(){return callback(new Error('Could queue swipes to save'))}, 100);
else
{
publication.on('error', console.error);
setTimeout(function(){ return callback();}, 100);
}
});
}
});
}
async.forEach(context.recommendations, likeRecommendation, function (error) {
if (!error) return done(null);
done(new Error('Could not like recommendations'));
});
}
The setTimeout will have to be placed at the end of the callback code.
Here is a possible solution (note that it doesn't use async.forEach)
var likeRecommendation = function (recommendation, callback) {
context.Client.like(recommendation._id, function (error, data) {
recommendation['drupal_user_uid'] = context.message.uid;
recommendation['drupal_user_uuid'] = context.message.uuid;
console.log(recommendation);
if (error) return done(new Error('Could not like recommendations'));
context.broker.publish('saves_swipes_publication', recommendation, function (err, publication) {
if (err) throw err
publication.on('error', console.error);
});
console.log('Liked!');
return callback()
});
}
function iterateWithTimeout(list, ctx, timeoutDuration) {
var currentIndex = 0;
(function invoke() {
list[currentIndex](ctx, function(error) {
if (!error) return done(null);
done(new Error('Could not like recommendations'));
setTimeout(function() {
if (++currentIndex < list.length) {
invoke();
}
}, timeoutDuration);
});
})();
}
// Interval is 1 second for now
iterateWithTimeout(context.recommendations, likeRecommendation, 1000);
So using "async.eachSeries" rather than "async.forEach" was the solution!
As always thanks for the help!

returning data from node mssql execute functions

I'm using mssql(Microsoft SQL Server client for Node.js) package from npm.I'm trying to execute a stored procedure residing in my sql server database.Everything works fine.However what I want to do is return the recordsets so that i can export this to be used in other module.Below is what I'm trying to do.
function monthlyIceCreamSalesReport (scope){
var connObj = connConfig();
connObj.conn.connect(function(err){
if(err){
console.log(err);
return;
}
connObj.req.input('Month',4);
connObj.req.input('Year',2016);
connObj.req.execute('<myStoredProcedure>', function(err, recordsets, returnValue){
if(err){
console.log(err);
}
else {
console.log(recordsets[0]); // successfully receiving the value
}
connObj.conn.close();
});
});
console.log('check for recordsets', recordsets[0]); // undefined
return recordsets[0];
}
var sqlServerObj = {
monICSalesReport : monthlyIceCreamSalesReport,
};
module.exports = sqlServerObj;
As shown in the code snippet, since the value of recordsets[0] is undefined, exporting this function is of no use.
You can't return this way in async nature. You can get it by passing the callback function
Try to give a callback function like this
function monthlyIceCreamSalesReport(scope, callback) { // pass a callback to get value
var connObj = connConfig();
connObj.conn.connect(function(err) {
if (err) {
console.log(err);
return;
}
connObj.req.input('Month', 4);
connObj.req.input('Year', 2016);
connObj.req.execute('<myStoredProcedure>', function(err, recordsets, returnValue) {
if (err) {
console.log(err);
} else {
console.log(recordsets[0]);
connObj.conn.close();
return callback(null, recordsets[0]); //return as a callback here and get that value in callback from where you called this function
}
});
});
}
var sqlServerObj = {
monICSalesReport: monthlyIceCreamSalesReport,
};
module.exports = sqlServerObj;
Note: See the comment to understand the changes
recordsets[0] is undefinded, because is defined only in connObj.req.execute function scope. You may do this in this way:
function monthlyIceCreamSalesReport (scope, cb){
var connObj = connConfig();
connObj.conn.connect(function(err){
if(err){
console.log(err);
return cb(Error("Something wrong"));
}
connObj.req.input('Month',4);
connObj.req.input('Year',2016);
connObj.req.execute('<myStoredProcedure>', function(err, recordsets, returnValue){
if(err){
console.log(err);
connObj.conn.close();
return cb(Error("Something wrong"));
}
else {
console.log(recordsets[0]); // successfully receiving the value
connObj.conn.close();
return cb(recordsets[0]);
}
});
});
}
var sqlServerObj = {
monICSalesReport : monthlyIceCreamSalesReport,
};
module.exports = sqlServerObj;

Node.js 'headers already sent' when page is refreshed

I keep getting this error (after a certain amount of time) when i refresh the my 'members area' after login. I use response.redirect to redirect to the area (success) or back to sign in form (error). How can i fix this?
app.get('/sellers/login', function(request, response) {
if(request.session.sellerId){
response.redirect( '/sellers/area?logged_in=true');
}
else{
response.render('pages/sellers-login');
}
});
app.post('/authenticate', function(request, response) {
if(request.session.sellerId){
response.redirect('/area?logged_in=true');
}
else{
db.authenticate(request.body.loginid, function(err, results) {
if(err){
response.redirect('/sellers/login?err=1&logged_in=false&type=db');
}
else{
if(results.length >=1){
var hash = results[0]['hash'];
var seller_id = results[0]['id'];
bcrypt.compare(request.body.password, hash, function(err, res) {
if(res){
request.session.sellerId = seller_id;
response.redirect('/sellers/area?logged_in=true');
}
else{
response.redirect('/sellers/login?err=1&logged_in=false&type=pMatch');
}
});
}
else{
response.redirect('/sellers/login?err=1&logged_in=false&type=user');
}
}
});
}
});
app.get('/sellers/area', function(request, response) {
if(request.session.sellerId){
response.render('pages/sellers-area');
}
else{
response.redirect('/sellers/login?not_logged_in=true');
}
});
if(request.session.sellerId){
response.redirect('/area?logged_in=true');
}
db.authenticate(request.body.loginid, function(err, results) {
// ....
});
You send the header when request.session.sellerId evaluates to true by calling the response.redirect, but you continue with the the db.authenticate afterwards.
Inside of that callback you will do another redirect, even when your redirect for that response has already taken place.
Inside of the db.authenticate you have the same problem with the if(err)
EDIT
you need to use an else block (or a return) for both the if (request.session.sellerId) and the if (err) {
if (request.session.sellerId) {
response.redirect('/area?logged_in=true');
} else {
db.authenticate(request.body.loginid, function(err, results) {
if (err) {
response.redirect('/sellers/login?err=1&logged_in=false&type=db');
} else if (results.length >= 1) {
var hash = results[0]['hash'];
var seller_id = results[0]['id'];
bcrypt.compare(request.body.password, hash, function(err, res) {
if (res) {
request.session.sellerId = seller_id;
response.redirect('/sellers/area?logged_in=true');
} else {
response.redirect('/sellers/login?err=1&logged_in=false&type=pMatch');
}
});
} else {
response.redirect('/sellers/login?err=1&logged_in=false&type=user');
}
});
}
I think i've found precisely where the error originates from which lies in the db.authenticate
//db.js
exports.authenticate = function(loginid, callback) {
var sql = "select ...";
pool.getConnection(function(err, connection) {
if(err) {
console.log(err);
callback(true);
return;
}
connection.query(sql, [loginid], function(err, results) {
connection.release();
if(err) {
console.log(err);
callback(true);
return;
}
callback(false, results);
});
connection.on('error', function(err) {
console.log(err);
connection.release();
callback(true);
return;
});
});
};
The connection.on('error',... gets called, so the callback(true) activates causing this error.

NodeJS passing an extra param to a callback

I'm a begginer in Node.JS and as a first tryout i'm implementing a small url shortening service that will get a request with an id parameter and redirect to the actual url after searching a cassandra database.
Below you can find my implementation.
var reqResponse;
app.get('/r/:id', function(req, res) {
reqResponse = res;
conn.connect(function(err, keyspace) {
if(err){
throw(err);
}
conn.cql(cqlSelectStatement, [req.params.id], { gzip:true }, redirectCallback);
});
});
function redirectCallback (err, results) {
if (err != null) {
//log
} else {
if (results.length > 0) {
results.every(function(row){
reqResponse.writeHead(config.redirectStatus, {
'Location': row[0].value
});
reqResponse.end();
return false;
});
} else {
reqResponse.send("There was a problem!");
return false;
}
}
conn.close();
return true;
}
It works fine, it does the job, but i'm having some doubts about that reqResponse "global" variable. I don't like it there.
Is there a way I could send "res" as a parameter to the redirectCallback function?
Thank you!
Yes there is: Create an anonymous function and use that to pass the parameter:
app.get('/r/:id', function(req, res) {
conn.connect(function(err, keyspace) {
if(err){
throw(err);
}
conn.cql(cqlSelectStatement, [req.params.id], { gzip:true }, function (err, results) { redirectCallback(err, res, results); } );
});
});
And your callback becomes:
function redirectCallback (err, res, results) {

Categories

Resources