Returned object undefined from module function - javascript

This has to be a scope issue that I'm not familiar with. I have a small module I've written as so:
(function () {
var getPlanInfo = function (id, conn) {
conn.query('SELECT * FROM `items` WHERE `id` = ?', [id], function (error, result) {
if (error) console.error('Query error: ' + error.stack);
console.log(result[0]); // Everything is great
return result[0];
});
};
modules.exports.getPlanInfo = function (id, conn) { return getPlanInfo(id, conn); // Typo }
})();
Here comes the problem. When I call it from anywhere (inside the module itself or another file), the return value is always undefined. I checked from within the function, the query returns the result as expected.
var backend = require('./module.js');
var t = backend.getPlanInfo();
t is undefined. This is the same if I call that method from inside the module itself (another function within that module).
I'm familiar with the callback principle in javascript and how objects and functions have to be passed around as an argument to remain in scope. Is this the issue here or is this a node.js particularity?
I tried in in the Developer Console (Chrome), works as expected.

conn.query() looks like it is async. Thus, you can't return its result from getPlanInfo() because getPlanInfo() returns long before the result is available. Returning result[0] from the conn.query() callback just returns a piece of data back into the conn.query() infrastructure. getPlanInfo() has long before already returned.
If you want an async result, then you will have to change getPlanInfo() to use a mechanism that supports getting async results such as a direct callback or a promise or something like that.
Here's a plain callback way:
var getPlanInfo = function (id, conn, callback) {
conn.query('SELECT * FROM `items` WHERE `id` = ?', [id], function (error, result) {
if (error) {
console.error('Query error: ' + error.stack);
callback(error);
return;
}
console.log(result[0]); // Everything is great
callback(0, result[0]);
});
};
modules.exports.getPlanInfo = getPlanInfo;
Then, the caller of that module would look like this:
var m = require('whatever');
m.getPlanInfo(id, conn, function(err, result) {
if (err) {
// error here
} else {
// process result here
}
});

You don't return anything from getPlanInfo. Probably you wanted to write modules.exports.getPlanInfo = function (id, conn) { return getPlanInfo; }
(with return getPlanInfo; instead of return getPlanInfo();)

Related

Solved Why do MySQL statements work normally in Node REPL when typed line by line, but not in a function? [duplicate]

In the code
var stuff_i_want = '';
stuff_i_want = get_info(parm);
And the function get_info:
get_info(data){
var sql = "SELECT a from b where info = data"
connection.query(sql, function(err, results){
if (err){
throw err;
}
console.log(results[0].objid); // good
stuff_i_want = results[0].objid; // Scope is larger than function
console.log(stuff_i_want); // Yep. Value assigned..
}
in the larger scope
stuff_i_want = null
What am i missing regarding returning mysql data and assigning it to a variable?
============ New code per Alex suggestion
var parent_id = '';
get_info(data, cb){
var sql = "SELECT a from b where info = data"
connection.query(sql, function(err, results){
if (err){
throw err;
}
return cb(results[0].objid); // Scope is larger than function
}
==== New Code in Use
get_data(parent_recording, function(result){
parent_id = result;
console.log("Parent ID: " + parent_id); // Data is delivered
});
However
console.log("Parent ID: " + parent_id);
In the scope outside the function parent_id is null
You're going to need to get your head around asynchronous calls and callbacks with javascript, this isn't C#, PHP, etc...
Here's an example using your code:
function get_info(data, callback){
var sql = "SELECT a from b where info = data";
connection.query(sql, function(err, results){
if (err){
throw err;
}
console.log(results[0].objid); // good
stuff_i_want = results[0].objid; // Scope is larger than function
return callback(results[0].objid);
})
}
//usage
var stuff_i_want = '';
get_info(parm, function(result){
stuff_i_want = result;
//rest of your code goes in here
});
When you call get_info this, in turn, calls connection.query, which takes a callback (that's what function(err, results) is
The scope is then passed to this callback, and so on.
Welcome to javascript callback hell...
It's easy when you get the hang of it, just takes a bit of getting used to, coming from something like C#
I guess what you really want to do here is returning a Promise object with the results. This way you can deal with the async operation of retrieving data from the DBMS: when you have the results, you make use of the Promise resolve function to somehow "return the value" / "resolve the promise".
Here's an example:
getEmployeeNames = function(){
return new Promise(function(resolve, reject){
connection.query(
"SELECT Name, Surname FROM Employee",
function(err, rows){
if(rows === undefined){
reject(new Error("Error rows is undefined"));
}else{
resolve(rows);
}
}
)}
)}
On the caller side, you use the then function to manage fulfillment, and the catch function to manage rejection.
Here's an example that makes use of the code above:
getEmployeeNames()
.then(function(results){
render(results)
})
.catch(function(err){
console.log("Promise rejection error: "+err);
})
At this point you can set up the view for your results (which are indeed returned as an array of objects):
render = function(results){ for (var i in results) console.log(results[i].Name) }
Edit
I'm adding a basic example on how to return HTML content with the results, which is a more typical scenario for Node. Just use the then function of the promise to set the HTTP response, and open your browser at http://localhost:3001
require('http').createServer( function(req, res){
if(req.method == 'GET'){
if(req.url == '/'){
res.setHeader('Content-type', 'text/html');
getEmployeeNames()
.then(function(results){
html = "<h2>"+results.length+" employees found</h2>"
html += "<ul>"
for (var i in results) html += "<li>" + results[i].Name + " " +results[i].Surname + "</li>";
html += "</ul>"
res.end(html);
})
.catch(function(err){
console.log("Promise rejection error: "+err);
res.end("<h1>ERROR</h1>")
})
}
}
}).listen(3001)
Five years later, I understand asynchronous operations much better.
Also with the new syntax of async/await in ES6 I refactored this particular piece of code:
const mysql = require('mysql2') // built-in promise functionality
const DB = process.env.DATABASE
const conn = mysql.createConnection(DB)
async function getInfo(data){
var sql = "SELECT a from b where info = data"
const results = await conn.promise().query(sql)
return results[0]
}
module.exports = {
getInfo
}
Then, where ever I need this data, I would wrap it in an async function, invoke getInfo(data) and use the results as needed.
This was a situation where I was inserting new records to a child table and needed the prent record key, based only on a name.
This was a good example of understanding the asynchronous nature of node.
I needed to wrap the all the code affecting the child records inside the call to find the parent record id.
I was approaching this from a sequential (PHP, JAVA) perspective, which was all wrong.
Easier if you send in a promise to be resolved
e.g
function get_info(data, promise){
var sql = "SELECT a from b where info = data";
connection.query(sql, function(err, results){
if (err){
throw err;
}
console.log(results[0].objid); // good
stuff_i_want = results[0].objid; // Scope is larger than function
promise.resolve(results[0].objid);
}
}
This way Node.js will stay fast because it's busy doing other things while your promise is waiting to be resolved
I've been working on this goal since few weeks, without any result, and I finally found a way to assign in a variable the result of any mysql query using await/async and promises.
You don't need to understand promises in order to use it, eh, I don't know how to use promises neither anyway
I'm doing it using a Model class for my database like this :
class DB {
constructor(db) {
this.db = db;
}
async getUsers() {
let query = "SELECT * FROM asimov_users";
return this.doQuery(query)
}
async getUserById(array) {
let query = "SELECT * FROM asimov_users WHERE id = ?";
return this.doQueryParams(query, array);
}
// CORE FUNCTIONS DON'T TOUCH
async doQuery(queryToDo) {
let pro = new Promise((resolve,reject) => {
let query = queryToDo;
this.db.query(query, function (err, result) {
if (err) throw err; // GESTION D'ERREURS
resolve(result);
});
})
return pro.then((val) => {
return val;
})
}
async doQueryParams(queryToDo, array) {
let pro = new Promise((resolve,reject) => {
let query = queryToDo;
this.db.query(query, array, function (err, result) {
if (err) throw err; // GESTION D'ERREURS
resolve(result);
});
})
return pro.then((val) => {
return val;
})
}
}
Then, you need to instantiate your class by passing in parameter to constructor the connection variable given by mysql. After this, all you need to do is calling one of your class methods with an await before. With this, you can chain queries without worrying of scopes.
Example :
connection.connect(function(err) {
if (err) throw err;
let DBModel = new DB(connection);
(async function() {
let oneUser = await DBModel.getUserById([1]);
let allUsers = await DBModel.getUsers();
res.render("index.ejs", {oneUser : oneUser, allUsers : allUsers});
})();
});
Notes :
if you need to do another query, you just have to write a new method in your class and calling it in your code with an await inside an async function, just copy/paste a method and modify it
there are two "core functions" in the class, doQuery and doQueryParams, the first one only takes a string as a parameter which basically is your mysql query. The second one is used for parameters in your query, it takes an array of values.
it's relevant to notice that the return value of your methods will always be an array of objects, it means that you'll have to do var[0] if you do a query which returns only one row. In case of multiple rows, just loop on it.

Function Return not Working

I am working in Node and trying to load the next sequence from my db. I am able to access the db, load and return the sequence within my function, but I am not able to access it outside of the function.
function getRunId() {
counters.findOne({_id: 'Run_ID'}, function(err, resp) {
if(err) {
console.log(err);
}
console.log('Seq: ' + resp.sequence); // Console Output = Seq: 1234
return resp.sequence;
});
};
var currentRunId = getRunId();
console.log('Run_ID: ' + currentRunId); // Console Output = CRID: undefined
I've checked several pages worth of Stack Overflow issues relating to using callback's, async (node module), how to properly return values in the function, etc... but none of them get me closer to accessing currentRunId outside of the function.
Is this issue further complicated by the use of Mongo queries inside my function?
For anyone stumbling on this later, start by reading this answer.
I've dealt with this a few times so I understand the frustration. You are trying to mix sync and async code by doing this:
var currentRunId = getRunId();
console.log('Run_ID: ' + currentRunId);
The trouble is that console.log('Run_ID: ' + currentRunId) is called immediately after you invoke getRunID() by assigning it to current RunID, and getRunID() resolves after console.log('Run_ID: ' + currentRunId), causing the currentRunId variable to be undefined.
But, you have some options to deal with this. Option one is to return a callback, and log the results of the callback instead. Option 2 is to use an ES6 promise. To use option 2, you need node version 7, and you need to use 'use strict' in your code.
Here are 3 examples built around a function stub that spoofs the results of findOne(). The getRunIdA() is your function, and getRunIdB, and getRunIdC are two example solutions to your current problem.
'use strict'
// A function stub which represents a simplified version of findOne.
// Accepts callback and returns a callback with the results of data
function findOne (callback) {
var data = {
sequence: 6
}
return callback(null, data)
}
// This is a simplified version of your function, which reproduces the undefined result
function getRunIdA () {
findOne(function (err, resp) {
if (err) {
console.log(err)
}
console.log('Seq: ' + resp.sequence)
return resp.sequence
})
}
// This is your function with a callback
function getRunIdB (callback) {
findOne(function (err, resp) {
if (err) {
console.log(err)
}
console.log('Seq: ' + resp.sequence)
return callback(resp.sequence)
})
}
// This is your function with a promise
var getRunIdC = new Promise(function (resolve, reject) {
resolve(findOne(function (err, resp) {
if (err) {
console.log(err)
}
return resp.sequence
}))
})
// Invoke your funciton; get undefined
var currentRunID = getRunIdA()
console.log('Run_ID: ' + currentRunID) // Run_ID: undefined
// Invoke getRunIdB using callback, get 6
getRunIdB(function (id) {
console.log('Run_ID: ' + id) // Run_ID: 6
})
// Invoke getRunIdC with a promise; get 6
getRunIdC.then(function (currentRunID) {
console.log('Run_ID: ' + currentRunID) // Run_ID: 6
})
/*
results for all 3:
Seq: 6
Run_ID: undefined
Seq: 6
Run_ID: 6
Run_ID: 6
*/
Give this a try by saving to your machine and running:
node test.js
Is this issue further complicated by the use of Mongo queries inside my function?
Nope, you just need to pass the results of your query to a promise or a callback so that you can work with the results somewhere else.
I hope this helps!
Edit: OP added the following code in a comment, which I will try to break down and address.
Unfortunately, using getRunIdB results in callback is not defined and using getRunIdC results in currentRunId is not defined
var currentRunID = '';
var getRunId = new Promise(function (resolve, reject) { resolve(counters.findOne({_id: 'Run_ID'}, function (err, resp) {
if (err) {
console.log(err)
}
return resp.sequence;
}))
});
getRunId.then(function (res) {
console.log('Run_ID: ' + res.sequence) // Run_ID: 1234
currentRunID = res.sequence;
})
console.log(currentRunID); // currentRunID is not defined
Check out an answer I gave to a similar question for more details on the JS concurrency model. Simply put, the getRunID() function is executing asynchronous code. What that means is that getRunID() doesn't get inserted into the message queue that determines what order javascript will execute until it's callbacks are completed. Thus, when you log currentRunID outside of the .then() function, the results is undefined because currentRunID is undefined.
I think that ultimately what OP is trying to do is to export the result of the function so that the something can be done with those results, this needs to be done within a callback like so:
getRunId.then(function (res) {
// Do stuff with the run ID here.
})
You are only returning on a callback function but not on the actual function.. Change your code to this:
function getRunId() {
var result = counters.findOne({_id: 'Run_ID'}, function(err, resp) {
if(err) {
console.log(err);
}
console.log('Seq: ' + resp.sequence); // Console Output = Seq: 1234
return resp.sequence;
});
return result; //<-- return result of your function is here
};
var currentRunId = getRunId();
console.log('Run_ID: ' + currentRunId);

Extract value from double callback

How can I assign value to variable in global from double callback?
First of, I read some value from file, when its done, I pass it to some fn in callback and want to use result value in initial scope, outside callback.
I can't wrap my head around this for some reason although at first it looks trivial.
var done = function(err, value) {
if (err) {
return;
}
var resultValue = someMethod(value);
};
loadFile(done);
var resultVal = ?? //result value needed here
function loadFile(done) {
fs.realpath(filePath, function (err, resolvedPath) {
if (err) {
return done(err);
}
fs.readFile(resolvedPath, function (err, value) {
if (err) {
return done(err);
}
return done(null, data);
});
});
}
As I said in my comment you are using an asynchronous call to load a file. You want the result of someMethod stored into the global variable resultVal. Except this isn't possible.
When you call loadFile(done) a asynchronous call is made to the server. This call is being resolved by an event. If the event returns 200 the server returned the expected answer. If their is an error it will be passed to done, if not the data will be passed. Let's say this takes about 250 ms to resolve.
In the mean time JavaScript continued parsing the code, because the call was asynchronous, running in a separate thread, thus not halting the execution of the main thread. The next line that gets parsed is returnVal. However the call isn't resolved yet because this line gets executed 1 ms after the function loadFile was called. This leaves a gap of 249 ms.
The solution is to rethink your code to cope with the asynchronous call.
var done = function(err, value) {
if (err) {
return;
}
var resultValue = callBack(value);
};
loadFile(done);
function someMethod(value)
{
//execute whatever you want to do here!
}
function loadFile(done) {
fs.realpath(filePath, function (err, resolvedPath) {
if (err) {
return done(err);
}
fs.readFile(resolvedPath, function (err, value) {
if (err) {
return done(err);
}
return done(null, data);
});
});
}
Of course you can provide the function done with the callback you want. Just look at this code:
var done = function(err, value, callBack) {
if (err) {
return;
}
var resultValue = someMethod(value);
};
loadFile(done, method1);
function method1(value)
{
//execute whatever you want to do here!
}
function loadFile(done, callBack) {
fs.realpath(filePath, function (err, resolvedPath) {
if (err) {
return done(err);
}
fs.readFile(resolvedPath, function (err, value) {
if (err) {
return done(err);
}
return done(null, data, callBack);
});
});
}
Instead of declaring resultValue as : var resultValue = someMethod(value);
You can do global.resultValue = someMethod(value);
This will make resultValue as a global variable.
You can access it anywhere using global.resultValue.
Similarly,instead of using global you can also use process.
global and process are global objects for nodejs just like window is for javascript.

Returning from anonymous function passed as parameter

Consider the following code:
function dbTask(q) {
mysql = new MySQL();
mysql.host = sqlHost;
mysql.user = sqlUser;
mysql.password = sqlPassword;
mysql.query("USE stock");
return mysql.query(q, function(err, results, fields) {
if(err) {
console.log("MySQL Error: " + err + ", Query: " + q);
return false;
} else {
return results; //here
}
});
};
var r = dbTask("SELECT * FROM user;");
console.log(r);
Whereas, I want the results to be returned from the inner anonymous function when calling dbTask() in the second last line, I am getting different output which seems like some internal construct of the mysql library under use.
How can I get dbTask to return the results when called?
Since mysql.query is asynchronous, then you'll have to re-think your architecture.
Instead of having your function return true or false, you'll have to pass in handlers to be called if the query returned true or false.
Something like this:
function dbTask(q, success, failure) {
mysql = new MySQL();
mysql.host = sqlHost;
mysql.user = sqlUser;
mysql.password = sqlPassword;
mysql.query("USE stock");
mysql.query(q, function(err, results, fields) {
if(err) {
console.log("MySQL Error: " + err + ", Query: " + q);
failure(err, results, fields);
} else {
success(results, fields);
}
});
};
Which you'd call like this:
dbTask(q,
function(results, fields) { /* ... */ },
function(err, results, fields) { /* ... */ });
You can't because dbTask returns once the mysql.query call completes to initiate the query. So by the time mysql.query's callback is called, console.log(r) has already executed.
This is the crux of the asynchronous nature of node.js.
What you can do is have dbTask accept a callback parameter that the function calls once results is available so that it can be provided to the caller asynchronously.

JavaScript scope issue

I have the following piece of javascript but for some scoping reason the "names" that is returned from myfunc is empty.
var myfunc = function(client, id) {
var names = new Array();
client.query(
'SELECT stuff FROM mytable WHERE id="'+id+'"',
(function selectCb(err, results, fields) {
if (err) {
throw err;
}
for (result in results) {
// This prints fine
console.log(results[result].name);
names[result] = results[result].name;
}
client.end();
})
);
// The following returns empty
return names;
}
console.log(myfunc(1,2));
How can I make it break out of scope?
It's empty because the call to your "query" function is asynchronous. The function you pass into it won't be run until the results are available. Therefore, your "myfunc" function returns immediately, long before that callback function is invoked.
Using Javascript in a browser, you have to think in those terms. Instead of expecting your "names" to be ready immediately, change "myfunc" so that you pass it a callback function to be invoked when the names are actually available:
var myfunc = function(client, id, whenFinished) {
var names = new Array();
client.query(
'SELECT stuff FROM mytable WHERE id="'+id+'"',
(function selectCb(err, results, fields) {
if (err) {
throw err;
}
for (result in results) {
// This prints fine
console.log(results[result].name);
names[result] = results[result].name;
}
client.end();
if (whenFinished) whenFinished(names); // callback
})
);
};
Now when you call your function, instead of expecting the "names" as a return value, you'll pass in another function that will act on the list of names:
myfunc(1, 2, function(names) { console.log(names); });
If client.query(...) is asynchronous, then the selectCb function would not have run and names would not have changed by the time myfunc returns. You need to redesign myfunc to return names asynchronously (by, for example, accepting a function parameter which it calls at the end of selectCb).
var names = new Array();
var myfunc = function(client, id) {
client.query(
'SELECT stuff FROM mytable WHERE id="'+id+'"',function selectCb(err, results, fields)
{
if (err) {
throw err;
}
for (result in results) {
// This prints fine
console.log(results[result].name);
names[result] = results[result].name;
}
client.end();
}
);
// The following returns empty
return names;
}
console.log(myfunc(1,2));
try making names global

Categories

Resources