Correctly using callbacks to wait for query completion - mysql

I'm trying to make a reusable piece of code that queries a database (with some parameters), then performs action on this data and returns some newly created data. What I have roughly looks like this:
function loadList(config) {
var list = [];
var queryString = "SOME QUERY STRING BASED ON THE config PARAMETER";
connection.query(queryString, function (err, result) {
if err throw err;
for (var i = 0; i < result.length; i++) {
//Perform some action on the data
//IMPORTANT this changes the list variable
}
});
return list;
}
Now, this code doesn't work, the function will in pretty much every case return []. That is because return list is executed long before the callback from the query has ran. However, I can't for the love of god figure out how to structure my code that the return list of the parent function runs after the query and callback both ave executed. It might be because I'm tired, but I really can't see the solution to it.
I have thought of making a new callback function inside loadList() that is called from within the query, but if I call return list from within that callback, it will only return list for that callback, not the parent function.
What is the correct way to implement this?

You should use callback base functions, like this:
function loadList(config, callback) {
// check that callback is not null
callback = callback || function() {};
var list = [];
var queryString = "SOME QUERY STRING BASED ON THE config PARAMETER";
connection.query(queryString, function(err, result) {
if (err) {
return callback(err);
}
for (var i = 0; i < result.length; i++) {}
// after you done your work call the call back
return callback(null, list);
});
}
Usage :
loadList(config_here, function(err, list) {
if (err) {
// do something with err
}
// here you have the list
})

Related

Node.js - Use asynchronous function to get a return value in a synchronous way without callbacks

I have a function to retrieve a list of UserID's from a mysql database.
function GetUsers(callback) {
UpdateLogFile('Function Call: GetUsers()')
var users = []
Database.execute( connectionStr,
database => database.query('select UserID from Users')
.then( rows => {
for (let i = 0; i < rows.length; i++){
users.push(rows[i].UserID)
}
return callback(users)
})
).catch( err => {
console.log(err)
})
}
For Reference:
Database class which came from here
const mysql = require( 'mysql' )
class Database {
constructor( config ) {
this.connection = mysql.createConnection( config )
}
query( sql, args ) {
return new Promise( ( resolve, reject ) => {
this.connection.query( sql, args, ( err, rows ) => {
if ( err )
return reject( err )
resolve( rows )
})
})
}
close() {
return new Promise( ( resolve, reject ) => {
this.connection.end( err => {
if ( err )
return reject( err )
resolve()
})
})
}
}
Database.execute = function( config, callback ) {
const database = new Database( config )
return callback( database ).then(
result => database.close().then( () => result ),
err => database.close().then( () => { throw err } )
)
}
After hours of learning about promises and callbacks, I was finally able to get GetUsers() to at least work and return what I'm looking for. However, I seem to only be able to use it as such:
GetUsers(function(result){
// Do something with result
})
But I would really like to be able to have a traditional return statement in the function so that I could use it like this: var users = GetUsers(). I have seen posts saying that this is impossible due to the nature of asynchronous functions but I am still hopeful since I would really like to be able to avoid callback hell. I tried the code below but "users" just results as undefined after execution. So, my main goal is to be able to get the return value from GetUsers() without chaining callbacks together since I have other functions that behave similarly. Is this possible?
var users
GetUsers(function(result){
users = result
})
console.log(users)
This is a very confusing topic, and it took me a while to really understand why what you are asking simply is not possible (at least, in the exact way you are asking). For the examples I will using python Django and Node.js to compare.
Sync
def synchronous():
print('foo') //this will always print first
print('bar')
def getUsers():
with connection.cursor() as cursor:
cursor.execute('SELECT * FROM USERS') //this query is executed
users = cursor.fetchall()
print('foo') //this doesn't trigger until your server gets a response from the db, and users is defined
print(users)
Async
function asynchronous() {
console.log('foo'); //this will also always print first
console.log('bar');
}
function getUsers() {
var connection = mysql.createConnection(config);
connection.query('SELECT * FROM USERS', function(error, users) { //this is a "callback"
console.log(users); //this will print
//everything inside of here will be postponed until your server gets a response from the db
});
console.log('foo') //this will print before the console.log above
console.log(users); //this will print undefined
//this is executed before the query results are in and will be undefined since the "users" object doesn't exist yet.
}
A callback is simply the function that your server is supposed to run once you get a response. We typically use the actual word "callback" like this:
function getUsers(callback) {
var connection = mysql.createConnection(config);
connection.query('SELECT * FROM USERS', function(error, users) {
if (error) throw error; //always do your error handling on the same page as your query. Its much cleaner that way
callback(users) //server asks what to do with the "users" object you requested
});
}
Now on somewhere else on your server:
getUsers(function(users) {// the callback gets called here
console.log(users); //do what you want with users here
});
The getUsers function takes some other function (ie a callback) as its argument and executes that function after you perform your query. If you want to do the same thing without using the word "callback", you can use an await/async function like fsociety, or you explicitly write out your code and not make functions that take other functions as their arguments.
This is functionality identical to the code from above:
var connection = mysql.createConnection(config);
connection.query('SELECT * FROM USERS', function(error, users) {
if (error) throw error;
console.log(users);
});
Callback hell is inevitable, but it really Isn't too bad once you get the hang of it.
Use an async-await function instead.
async function GetUsers(callback) {
try {
UpdateLogFile('Function Call: GetUsers()')
var users = []
let rows = await Database.execute( connectionStr,
database => database.query('select UserID from Users')
for (let i = 0; i < rows.length; i++){
users.push(rows[i].UserID)
}
return callback(users)
} catch(err) {
console.log(err)
}
}
Hope this helps!

Calling a function which calls Async mysql

I want to call a function that executes a query using npm-mysql .query function. The problem is that .query is asynchronous so I get a returned value = undefined and after that the mysql.query finishes the execution.
I 've tried to use promises but I couldn't synchronize the return value with the mysql.query result.
I don't want to use sync-mysql.
I want it to be in a wrapper function as shown.
function mysql_select(query)
{
var json_result
mysql_connnection.query(query, function (err, result)
{
if (err) throw err
json_result = JSON.stringify(result)
})
return json_result
}
For example i want to call this function like this:
console.log(mysql_select("SELECT * FROM table"))
and dont get the undefined result
I have checked the query , it returns the data correctly but after the function returns the json_result.
You might want to have a look into Promises:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
function mysql_select(query)
{
return new Promise(function(resolve, reject) {
mysql_connnection.query(query, function (err, result)
{
if (err) {
reject(err);
return;
}
resolve(JSON.stringify(result));
})
})
}
;(async function() {
console.log(await mysql_select('SELECT * FROM table'));
})();

How to retrieve of data from MySQL server for all items in a list in nodejs?

I am making a graph based web progrom. I am using nodejs.
I have a list of keys calls map which stores ID of vertices.
I want to retrieve the name of these vertices from MySQL using the ID. I have found a solution but I am not sure if it will work every time. This is my code.
for(var i=0;i<map.length;++i){
con.query('SELECT * FROM station WHERE id='+map[i],function(err,result,field){
if(err)
console.log("ERROR 3");
else{
result.forEach(function(r){
stationName.push(r.name);
})
if(stationName.length==map.length){
console.log(stationName);
res.render('route/showroute.ejs',{stationName: stationName});
}
}
})
}
I was wonder is it possible that my last map query loads before other query which may cause station name to be stored in wrong order. I am new to javascript.
You can do this using async await function
var getDataById = function (id) {
return new Promise(function (resolve, reject) {
con.query('SELECT * FROM station WHERE id=' + id, function (err, result, field) {
if (err) {
console.log("ERROR 3");
reject(err);
} else {
resolve(result);
}
});
});
};
(async function () {
for (var i = 0; i < map.length; ++i) {
var data = await getDataById(map[i]);
data.forEach(function (r) {
stationName.push(r.name);
});
if (stationName.length == map.length) {
console.log(stationName);
}
}
})();
You are worried about the query-function being asynchronous, and it is, but only with regard to what comes after the function. You're putting your stationNames into a new array inside the callback function which will be executed in sequence.

NodeJs MySql multiple update

I have a method in NodeJs using Express framework, in which I am iterating an array and making an update in a Mysql DB,
The Code receives a Connection object and a Post Body,
The Post Request Body is an Array of objects, of the data to be saved in the DB,
I am trying to loop the objects one by one and save them in the DB using an Update Query.
Now the strange part is, the code works only if it gets called twice immediately,
Ie. On testing I found out, I have to make the API request twice in order for the code to save the data.
I get the following error on the first API call -
Error Code: 1205. Lock wait timeout exceeded; try restarting transaction
It's a simple Update call, I checked the MySql processes and there was no deadlock,
SHOW FULL PROCESSLIST;
But the same code work's on the immediate 2nd API call.
let updateByDonationId = async (conn, requestBody, callback) => {
let donations = [];
donations = requestBody.donations;
//for(let i in donations){
async.each(donations, function(singleDonation, callback) {
//let params = donations[i];
let params = singleDonation;
let sqlData = []
let columns = "";
if(params.current_location != null){
sqlData.push(params.current_location);
columns += "`current_location` = ?,";
}
if(params.destination_location != null){
sqlData.push(params.destination_location);
columns += "`destination_location` = ?,";
}
if(columns != ''){
columns = columns.substring(0,columns.length-1);
let sqlQuery = 'UPDATE donation_data SET '+columns
+' WHERE donation_id = "' + params.donation_id + '"';
conn.query(sqlQuery, sqlData, function (err, result) {
logger.info(METHOD_TAG, this.sql);
if (err) {
logger.error(METHOD_TAG, err);
return callback(err, false);
}
})
}
else{
return callback(null, false);
}
columns = "";
sqlData = [];
},
function(err, results) {
if (err) {
logger.error(METHOD_TAG, err);
return callback(err, false);
}
else{
return callback(null, true);
}
});
//return callback(null, true);
} // END
Also referring the following, i guess he was getting an ER_LOCK_WAIT_TIMEOUT for weird reason as well -
NodeJS + mysql: using connection pool leads to deadlock tables
The issue seems to be with the Non blocking Async nature of Node as rightly pointed out
Can anyone help with a correct code?
I'd say the asynchronous nature of Node.js is going to be causing you issues here. You can try rewriting your loop. You can either use promises or the async.eachSeries method.
Try changing your loop to use the below:
async.eachSeries(donations, function(singleDonation, callback) {
.query() the method is asynchronous, because of this your code tries to execute one query after another without waiting for the former to finish. On the database side, they just get queued up if they happen to affect the same portion of it, i.e., one query has a "Lock" on that portion of the database. Now one of the transactions has to wait for another to finish and if the wait is longer than the threshold value then the error which you are getting is caused.
But you said that you are not getting the error on the second immediate call, my guess is that during first call(s) the data was cached so therefore the second call was faster and it was fast enough to keep the wait under threshold value thus the error was not caused on the second call.
To avoid this all together and still maintain the asynchronous nature of code you can use Promise along with async-await.
The first step is to create a Promise based wrapper function for our .query() function, like so:
let qPromise = async (conn, q, qData) => new Promise((resolve, reject) => {
conn.query(q, qData, function (err, result) {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
Now here is your modified function which uses this Promise based function and async-await:
let updateByDonationId = async (conn, requestBody, callback) => {
let donations = [];
donations = requestBody.donations;
try {
for (let i in donations) {
let params = donations[i];
let sqlData = [];
let columns = "";
if (params.current_location != null) {
sqlData.push(params.current_location);
columns += "`current_location` = ?,";
}
if (params.destination_location != null) {
sqlData.push(params.destination_location);
columns += "`destination_location` = ?,";
}
if (columns != '') {
columns = columns.substring(0, columns.length - 1);
let sqlQuery = 'UPDATE donation_data SET ' + columns
+ ' WHERE donation_id = "' + params.donation_id + '"';
let result = await qPromise(conn, sqlQuery, sqlData);
logger.info(METHOD_TAG, result); // logging result, you might wanna modify this
}
else {
return callback(null, false);
}
columns = "";
sqlData = [];
}
} catch (e) {
logger.error(METHOD_TAG, e);
return callback(err, false);
}
return callback(null, true);
}
I wrote the code on fly so there maybe some syntactical error(s).

Problem with mysql wrapper function

I'm playing with a Node.JS app for the first time, and so far I love it, but I'm having a problem...
I have written a wrapper function on the client.query function that simply allows me to pass a parameter and it gets the query from an array of allowed queries...
var runProcedure = function(proc, vars){
var params;
if(typeof vars != 'undefined'){
params.concat(eval('(' + vars + ')'));
}
this._client.query(queries[proc], params, function(err, results, fields){
if(err) { throw err; }
if(results){
return(results);
}else{
return "No data found.";
}
});
}
The function works correctly and if I console.log results, the data I want is there.. however, it's not getting returned to where I called it...
var data = runProcedure(procedureName, parameters);
console.log(data); // undefined
While troubleshooting, it seems that the query function is run asynchronously.... but this causes me a big problem. The runProcedure function is being called from within an http request handler.. so I need to be able to access the response variable. I guess I could pass it all the way down as a parameter... but that seems clumsy. What is the best code pattern to handle this? Should I set the response as a global var? can I run the mysql synchronously?
Cheers,
whiteatom
just pass your data to callback instead of returning with return
var runProcedure = function(proc, vars, callback) {
var params;
if(typeof vars != 'undefined'){
params.concat(eval('(' + vars + ')'));
}
this._client.query(queries[proc], params, function(err, results, fields){
if(err) { throw err; }
if(results){
callback(results);
}else{
callback('No data found.');
}
});
}
Now in http request handler:
// ...
function httpRequestHandler(req, resp)
{
//...
runProcedure(proc, vars, function(dbResp) {
if (dbResp === 'No data found.')
{
// handle error
} else {
// do something with result
}
})
}