Functions are still being called asynchronously. What am I doing wrong? - javascript

My code is supposed to download 5 files exported from our school's database, then run a query based on the first export (there will be queries for the other four files, and there are three schools, so my functions need to be scalable).
I have a function called "download" and another function called "updateSchedule." Both of these functions work separately.
var download = function(file){
var deferred = q.defer();
var i = 1;
var ftpGet = function(){
var number = i++;
toString(number);
filename = file+number+'.csv';
ftp.get(filename, filename, function(hadErr){
if (hadErr){
console.error('There was an error retrieving ' + filename);
}else{
console.log(filename + ' downloaded');
if(i <= 5){
ftpGet();
}else{
deferred.resolve();
}
}
});
}
ftpGet();
return deferred.promise;
}
var updateSchedule = function(school, school_id){
var deferred = q.defer();
console.log('');
connectionpool.getConnection(function(err, connection){
if(err){
console.error('CONNECTION error: ', err);
}else{
connection.query('DELETE FROM schedule WHERE school_id = "'+school_id+'"', function(err, rows){
if (err) console.error(err);
var path = './'+school+'_export1.csv';
var reader = csv.createCsvFileReader(path, {'separator': ',','quote': '"','excape': '"','comment': ''});
reader.addListener('data',function(data){
connection.query('INSERT INTO schedule SET section_id = "'+data[0]+'", student_id = "'+data[1]+'", course_number = "'+data[2]+'", period = "'+data[3]+'", teacher_id = "'+data[4]+'", school_id = "'+school_id+'"', function(err, rows){
if (err) console.error(err);
deferred.resolve();
});
});
});
connection.release();
console.log(school+' schedule updating...');
}
});
return deferred.promise;
}
When I call them using promises, however, I get an error saying it can't open the file. What am I doing wrong?
download('lca_export').then(updateSchedule('lca', '517'));

Change this:
download('lca_export').then(updateSchedule('lca', '517'));
to this:
download('lca_export').then(function() {
updateSchedule('lca', '517');
});
You were calling updateSchedule() immediately and passing the result to .then() rather than passing a function reference to .then() that can be called later. Thus, updateSchedule() was running before download() had done its work. This is a very common mistake. Just remember that if there are parens after the function name it will execute immediately. If it's just a function name or an anonymous declaration without parens, then you're passing a function reference.
Some other things to cleanup:
Add var in front of filename to make it a local variable rather than an implicit global.
Remove the toString(number). At best it isn't needed at all and isn't doing anything since you aren't assigning the result to anything. At worst, it's causing an error because there is no function toString(). FYI, numbers are automatically converted to strings when added to a string so you do not need to do that manually.

Related

Node.js callback function doesn't work

I am a beginner to node.js and I have been working on a simple cube timer but unfortunately, something doesn't work. In app.get when I run sumOfSeconds(best) an error occurs telling me that fun (last line in sumOfSolves) is not a function. However when I tried running sumOfSeconds outside of app.get (i also change the return to console.log so I can see the result) everything works. What is the problem?
Thanks in advance ;)
var sumOfSolves = [];
//create an array of total seconds in each time - used for Ao5, Ao12, best and worst
function sumOfSeconds(fun){
Time.find({}, function(err, data){
if(err) throw err;
sumOfSolves = [];
for(var i = 0; i < data.length; i++){
sum = data[i].min * 60 + data[i].sec;
sumOfSolves.push(sum);
}
fun(); //this makes best(), worst(), Ao5(), Ao12 execute after sumOfSeconds finished everything else,
//otherwise those functions run before the sumOfSolves array had something in ot and therefore the code didn't work
})
}
function best(){
var best = Math.min(...sumOfSolves);
var position = sumOfSolves.indexOf(best);
Time.find({}, function(err, data){
if(err) throw err;
var bestTime = data[position].time;
return bestTime
})
}
function worst(){
var worst = Math.max(...sumOfSolves);
var position = sumOfSolves.indexOf(worst)
Time.find({}, function(err, data){
if(err) throw err;
var worstTime = data[position].time;
return worstTime;
})
}
function Ao5(){
}
function Ao12(){
}
module.exports = function(app){
app.get('/', function(req, res){
var best = sumOfSeconds(best);
var worst = sumOfSeconds(worst);
Time.find({}, function(err, data){
if(err) throw err;
res.render('index', {times: data, best: best, worst: worst});
})
});
You declare best and worst as var in app.get. Don't do this! Choose unique names for your variables that don't conflict with the function declarations.
Edit:
Scope in Javascript is as important as in any other language, maybe more so because the entire script is essentially global scope (the global scope has access to variables declared in a nested scope).
Also, almost everything is a JS Object. (See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures for the 6 primitive types.) So, unlike in languages that allow both a variable and a function to have the same name, in JS, there can be only one identifier of a given name. You "hoisted" the names best and worst, so the JS engine used your new definition instead of the functions.
If you want to avoid these kinds of issues in the future, and don't have to worry about supporting older browsers, use let instead of var. let warns you about hoisting.
Also, at least during development, put: "use strict"; at the top of your source file.

Handle async DB calls

I did a couple of projects with node.js and I'm aware of the async behaviour and that one should usually use callback functions, etc. But one thing that bothers me ist the following.
I'm developing an Alexa skill and I have a function that handles the User intent:
'MyFunction': function() {
var toSay = ""; // Holds info what Alexa says
// Lot of checks and calculations what needs to be said by Alexa (nothing special)
if(xyz) {
toSay = "XYZ";
}else if(abc) {
toSay = "ABC";
}else{
toSay = "Something";
}
// Here is the "tricky" party
if(someSpecialEvent) {
toSay += " "+askDatabaseForInput(); // Add some information from database to string
}
this.emit(':ask', toSay, this.t('REPROMT_SPEECH')); // Gives the Info to Alexa (code execution stops here)
}
As mentioned in the code, there is some code which is usually used to find out what the output to Alexa should be.
Only on rare events, "someSpecialEvent", I need to query the database and add information to the String "toSay".
Querying the DB would look something like:
function askDatabaseForInput() { // The function to query the DB
var params = {
TableName: "MyTable",
OtherValues: "..."
};
// Do the Query
docClient.query(params, function(err, data) {
// Of course here are some checks if everything worked, etc.
var item = data.Items[0];
return item; // Item SHOULD be returned
});
return infoFromDocClient; // Which is, of course not possible
}
Now I know, that in the first function "'MyFunction'" I could just pass the variable "toSay" down to the DB Function and then to the DB Query and if everything is fine, I would do the "this.emit()" in the DB Query function. But for me, this looks very dirty and not much reusable.
So is there a way I can use "askDatabaseForInput()" to return DB information and just add it to a String? This means making the asynchronous call synchronous.
Making a synchronous call wouldn't affect the user experience, as the code isn't doing anything else anyway and it just creates the String and is (maybe) waiting for DB input.
Thanks for any help.
So you could do 2 things:
Like the person who commented says you could use a callback:
function askDatabaseForInput(callback) {
var params = {
TableName: "MyTable",
OtherValues: "..."
};
docClient.query(params, function(err, data) {
if (err) {
callback(err, null)
} else {
var item = data.Items[0];
callback(null, item);
}
});
}
or you could use promises:
function askDatabaseForInput() {
var params = {
TableName: "MyTable",
OtherValues: "..."
};
return new Promise(function (resolve, reject) {
docClient.query(params, function(err, data) {
if (err) {
reject(err)
} else {
var item = data.Items[0];
resolve(item);
}
});
});
}
you can then either put a function in where you call askDatabaseForInput or do askDatabaseForInput.then(....).
In the function or the .then you would add what you retrieved from the database to the variable toSay
hope this helps

Passing csvtojson converter's value to a variable outside of its scope

I'm currently fiddling around with Node.js and I stuck with this issue.
I'm using the csvtojson converter (https://github.com/Keyang/node-csvtojson) as a separate module that I can call in my other JS files as many times as I want.
Here is my tools.js:
module.exports = {
csvToJson: function (csvPath) {
var Converter = require('csvtojson').Converter;
var converter = new Converter({});
var transfer = "DEFAULT";
converter.fromFile(csvPath, function(err, result){
if (err) {
return console.log(err);
}
else {
transfer = result;
}
});
return transfer;
}
};
And here is how I call it:
var countriesCsvFile = path.join(__dirname, '..', 'testDataFiles', 'countries.csv');
//GRAB TOOLS
var tools = require('../app/tools');
console.log(tools.csvToJson(countriesCsvFile));
The result is always the "DEFAULT" value which indicates, that the converter is not touching it.
I want to pass it as the return value of the function, to further be able to process the data on the fly, without creating a file, and read that.
It is surely some scope issue, but after scratching my scalp for a few hours, and browsing the questions I couldn't retrieve anything remotely useful.
Also, another note: If I call console.log(result) instead of transfer = result, it shows me my precious and desired data.
You have to pass in a callback function because the csvToJson function is returning 'transfer' before any value is assigned to it. Like Sirko said, it's asynchronous. You can also use promises instead of callbacks but that's another topic in itself.
module.exports = {
csvToJson: function (csvPath, callback) {
var Converter = require('csvtojson').Converter;
var converter = new Converter({});
converter.fromFile(csvPath, function(err, result){
if (err) {
callback(err);
}
else {
callback(null, result);
}
});
}
};

How to return a nodejs callback as a number, from a JSON object property value?

I am trying to do maths on a number within a JSON object (the price of a stock ticker).
I want it to be a variable called 'btcusd_price', that I can then use to do arithmetic with.
How do I get a variable i can work with?
https://tonicdev.com/npm/bitfinex
var Bitfinex = require('bitfinex');
var bitfinex = new Bitfinex('your_key', 'your_secret');
var btcusd_price;
btcusd_price = bitfinex.ticker("btcusd", function(err, data) {
if(err) {
console.log('Error');
return;
}
console.log(data.last_price);
return data.last_price;
});
typeof btcusd_price;
console.log(btcusd_price); //i'm trying to work with the price, but it seems to be 'undefined'?
You have to set the values when they are available, the code is async and you were using the value before it is applied to btcusd_price. Note that the proper way to use the variable is when the callback executes its code.
Please, see the working code below:
Bitfinex = require('bitfinex');
var bitfinex = new Bitfinex('your_key', 'your_secret');
var btcusd_price;
bitfinex.ticker("btcusd", function(err, data) {
if(err) {
console.log('Error');
return;
}
btcusd_price = data.last_price;
console.log(typeof btcusd_price);
console.log(btcusd_price);
});

NodeJS Can't Access Object Scope in Children [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 7 years ago.
I'm having some scope issues when trying to save my return data to a parent scope. Here is my source, any help would be greatly appreciated. I can not for the life of me get table to = data.
My data var console.logs correctly its just a problem of scope.
function OpenDB(mongoUrl, callBack){
var MongoClient = mongodb.MongoClient;
var url = mongoUrl || "mongodb://" + process.env.IP + "/test";
MongoClient.connect(url, function(err, db) {
if(err){
console.log(err);
}
console.log(" Connected correctly to server ");
callBack(db, function(){
console.log(" Disconnected from server ");
db.close();
});
}.bind(this));
}
var GetTableAsArray = function(tableName){
var table = [];
OpenDB(null, function(db,cb){
db.collection(tableName).find().toArray(function(err, data){
if(err){
console.log(err);
}
//this is the problem
table = data;
cb();
});
});
return table;
};
By the time GetTablesAsArray function returns, table is still just an empty array. The problem here is that your query happens in an asynchronous way, meaning that you're code doesn't wait for it to be done before proceeding. You can use a callback to execute whatever code you want with the value of tables once it is fetched.
var GetTableAsArray = function(tableName, callback){
OpenDB(null, function(db,cb){
db.collection(tableName).find().toArray(function(err, data){
if(err){
console.log(err);
}
//this is the problem
table = data;
cb();
callback (data);
});
});
};
GetTableAsArray('tableName', function (table) {
console.log(table);
});

Categories