So what i am trying to do is, create a function, use mysql(don't care how, but i found an npm for it so using that for now.) to get information from my database and then feed my json with it. It works fine as long as the console.log() is in the right spot, but i have to return it so i have to move it further down. But when i do that, it won't show in my other file, which i need it to be at.
I have 2 of the basically same script, one is just for another faction.
- So i will only show one, as if i find a solution for one of them, i have a solution for both.
get_ally_online_players: function (guild) {
con.query("SELECT * FROM user_dbs WHERE guild='"+ guild +"'", function (err, result, fields) {
var con2 = mysql.createConnection({
host: result[0].host,
user: result[0].username,
password: result[0].password,
database: result[0].database
})
con2.connect()
con2.query("SELECT * FROM characters WHERE online='1' AND race=1 OR race=3 OR race=4 OR race=7 OR race=11", function (err, result, fields) {
allycount = 0
console.dir(result)
result.forEach(function(result) {
allycount = allycount+1
})
return allycount
});
con2.end()
});
}
Here is where i try to have it:
if(recieved.content === "!status")
{
var horde_players
var ally_players
horde_players == guilddb.get_horde_online_players("585919060722712670")
ally_players == guilddb.get_ally_online_players("585919060722712670")
console.log(guilddb.test())
//console.log(ally_players)
recieved.channel.send(ally_players + " : " + horde_players)
}
I have tried so many things... I've also read that you have to use callbacks since it's apparently going too fast, like it can't query before...
I'd like to stay away from the callback though, since it seems like a hassle to have inside the script. But if it's absolutely necessary, so be it. It returns with "undefined" btw. I have also tried to use promises instead and put a setTimeout function etc.
First of all, sorry about my English.
Actually you are commiting some mistakes.
You where commiting a mistake asigning values to horde_players and ally_players values
if (recieved.content === "!status") {
var horde_players
var ally_players
horde_players = guilddb.get_horde_online_players("585919060722712670")
ally_players = guilddb.get_ally_online_players("585919060722712670")
console.log(guilddb.test())
//console.log(ally_players)
recieved.channel.send(ally_players + " : " + horde_players)
}
With (==) you are comparing values, not setting.
Now lets talk about async operations.
I prepared an example. This is like in your case, you are trying to run two async operations that may finish at different times, but you need both of them finished before running something else. Try to run it:
function async1(id, callback) {
setTimeout(function() {
callback('The user with ' + id + ' is called Sergio');
}, 6000);
}
function async2(id, callback) {
setTimeout(function() {
callback('The user with ' + id + ' is called Jack');
}, 2000);
}
var result1;
var result2;
async1(1, function(result) {
result1 = result;
});
async2(2, function(result) {
result2 = result;
});
console.log('Finished result: ' + result1 + result2); // Finished result: undefined undefined
The finished result was run before finishing both async operations. Bad...
With callbacks, we need to nest the functions (if you had to do more async operations, you could lead into Callback Hell)
function async1(id, callback) {
setTimeout(function() {
callback('The user with ' + id + ' is called Sergio');
}, 6000);
}
function async2(id, callback) {
setTimeout(function() {
callback('The user with ' + id + ' is called Jack');
}, 2000);
}
var result1;
var result2;
async1(1, function(result) {
result1 = result;
async2(2, function(result) {
result2 = result;
console.log('Finished result: ' + result1 + result2); // 'Finished result: The user with 1 is called SergioThe user with 2 is called Jack
});
});
For your case, this would be my solution. Maybe I would improve more things like using const-let and ES6 features (async-await, arrow functions...), passing db connectors trought params... but maybe it's easier to you to understand now
var guilddb = {
get_horde_online_players: function(guild, callback) {
callback('Implement me!');
},
get_ally_online_players: function(guild, callback) {
con.query("SELECT * FROM user_dbs WHERE guild='" + guild + "'", function (err, result, fields) {
var con2 = mysql.createConnection({
host: result[0].host,
user: result[0].username,
password: result[0].password,
database: result[0].database
});
con2.connect();
con2.query("SELECT * FROM characters WHERE online='1' AND race=1 OR race=3 OR race=4 OR race=7 OR race=11", function (err, result, fields) {
var allycount = 0;
console.dir(result);
result.forEach(function(result) {
allycount = allycount+1
});
con2.end();
callback(allycount);
});
});
}
}
if (recieved.content === "!status") {
guilddb.get_horde_online_players("585919060722712670", function(result) {
var horde_players = result;
guilddb.get_ally_online_players("585919060722712670", function(result) {
var ally_players = result;
console.log(guilddb.test());
console.log(ally_players);
recieved.channel.send(ally_players + " : " + horde_players);
});
});
}
Hope it helps.
Your query is asynchronous so the function will implicitly return undefined before the query finishes.
Therefore, you will need to use callbacks or promises.
You can use callbacks like this:
// Accept a callback function as an argument
get_ally_online_players: function (guild, callback) {
con.query("SELECT * FROM user_dbs WHERE guild='"+ guild +"'", function (err, result, fields) {
var con2 = mysql.createConnection({
host: result[0].host,
user: result[0].username,
password: result[0].password,
database: result[0].database
})
con2.connect()
con2.query("SELECT * FROM characters WHERE online='1' AND race=1 OR race=3 OR race=4 OR race=7 OR race=11", function (err, result, fields) {
allycount = 0
console.dir(result)
result.forEach(function(result) {
allycount = allycount+1
})
// Pass the result to the callback function
callback(allycount)
})
con2.end()
})
}
...and then pass a callback function to get_ally_online_players that will receive the data when ready:
if (recieved.content === "!status") {
var horde_players
var ally_players
horde_players = guilddb.get_horde_online_players("585919060722712670")
// Pass a callback function that will receive the data when ready
guilddb.get_ally_online_players("585919060722712670", function(ally_players) {
console.log(ally_players)
recieved.channel.send(ally_players + " : " + horde_players)
})
}
I hope this helps.
Related
I'm quite new to NodeJS and JS globally and I'm in trouble while setting and Object Property through a MySQL query.
I'm using Promise to avoid bad asynchronous effect but apparently I'm doing it wrong, the property of my Agent Obejct is never updated.
Here is the code :
class Agent {
constructor(agentId, agentName, agentCountry) {
this.agentId = agentId;
this.agentName = agentName;
this.agentCountry = agentCountry;
}
setAgentCountry () {
var promise = function(agentID) {
return new Promise(function(resolve, reject) {
var query = "SELECT c.CountryID, c.CountryName FROM AgentCountry ac, Country c WHERE ac.AgentID = '" + agentID + "' AND ac.CountryID = c.CountryID";
connection.query(query, function(err, results) {
if (!err) {
resolve(results);
} else {
console.log('Error while performing Query.');
}
});
});
}
promise(this.agentID).then(function(data) {
var string = JSON.stringify(data);
var json = JSON.parse(string);
//the agent property is never updated !!
this.agentCountry = json;
}.bind(this), function(err) {
console.log(err);
});
}
}
I call the method this way :
var agent = new Agent(1,"John Doe", "France");
console.log(agent.agentCountry); //Displays "France"
agent.setAgentCountry();
console.log(agent.agentCountry); //Did not display the table of countries it should
Could you help me with this ?
Thanks
The main problem is that console.log is being executed before the promise being resolved. Writing a console.log inside the "then" clause will show you the timing.
The promise will be resolved or rejected eventually but nobody is waiting for setAgentCountry.
There are several points of order here:
A promise must always be either (1) resolved or (2) rejected. Your error case logs it to the console without calling reject(), so it's stuck in promise limbo for forever when it errors.
Why do you name a variable, promise, the same as the library, Promise?
I think you will find it more modular to just wrap the mysql_conn.query() callback into a promise():
const mysql_conn = mysql.createConnection({
host: mysql_conf.host,
user: mysql_conf.user,
password: mysql_conf.password
});
mysql_conn.queryPromiser = function(sql, args) {
return new Promise(function(resolve, reject) {
mysql_conn.query(
sql,
args,
function(err, results, fields) {
if (err) {
reject(err);
} else {
resolve( {"results": results, "fields": fields} );
}
}
);
});
};
then you can use it like so:
class Agent {
constructor(agentId, agentName) {
this.agentId = agentId;
this.agentName = agentName;
this.agentCountry = null;
}
configureCountryPromiser() {
var sql = "SELECT country FROM agent_countries WHERE agent_id = ?";
var args = [ this.agentId ];
var that = this;
return mysql_conn.queryPromiser(sql, args)
.then(function(data) {
if (data.results.length) {
that.agentCountry = data.results[0].country;
} else {
// handle case where agent_id is not found in agent_countries
}
});
}
};
agent_instance = new Agent(1, "Benoit Duprat");
agent_instance.configureCountryPromiser()
.then(function() {
console.log("agent country configured to ", agent_instance.agentCountry);
}).catch(console.error);
Please note that I have not tested the class code, but the general idea should be enough to answer your question.
I created 4 Lambda functions to process information that will be written into a MySQL table. the first three function just select, insert and update a MYSQL table record respectively.
I then created a 4th function to accept the record detail as part of the event parameter. This function will first try to select the record by invoking the first lambda function and if it finds it, will update the record on the table using the update lambda function. If it does not find it, it will invoke the insert function to add the record. I am using pool.query on the 3 functions that manipulates the MySQL table. I am also using lambda.invoke to call those three functions from the 4th function.
I was able to successfully test the 4th function locally by passing the record details as parameter and it was able to successfully call the three Lambda function and update the mySQL table record. The problem that I am having is that when I upload the function in AWS Lambda, it does not invoke any of the three functions. I am not seeing any errors in the log so I don't know how to check where the problem is. Here's ,y code that invokes the other functions:
exports.handler = (event, context, callback) => {
var err = null;
var payload = {
qryString : event.qryString,
record: event.updaterecord,
dbConfigPool : event.dbConfigPool
}
var params = {
FunctionName: 'getInventory',
Payload: JSON.stringify(payload)
}
console.log(' before invoke ' + JSON.stringify(params) )
lambda.invoke(params, function(err, data) {
console.log(' aftr invoke ' + JSON.stringify(params) )
if (err) {
console.log('err ' + err, err.stack); // an error occurred
event.message = err + ' query error';
}
else {
console.log('success' + JSON.stringify(data));
console.log(' status code ' + JSON.stringify(data.StatusCode));
console.log(' Payload ' + JSON.stringify(JSON.parse(data.Payload)));
var rowsTemp = JSON.parse(data.Payload);
var rows = rowsTemp.data;
if (!rowsTemp.recordExist) {
console.log('insert')
// Update inventory record only if quantity is not negative
var newQuantity = 0
newQuantity = parseFloat(event.updaterecord.quantity);
if (Math.sign(newQuantity) === 1) {
var payload = {
record: event.updaterecord,
dbConfigPool : event.dbConfigPool
}
console.log('insert' + JSON.stringify(payload));
var params = {
FunctionName: 'insertInventory',
Payload: JSON.stringify(payload)
}
lambda.invoke(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
}
}
else {
newQuantity = 0
newQuantity = parseFloat(event.updaterecord.quantity) + parseFloat(rows[0].quantity);
if (Math.sign(newQuantity) === 1) {
event.updaterecord.quantity = newQuantity;
} else {
// Set to zero if the result is negative
event.updaterecord.quantity = 0;
}
console.log('value ' + JSON.stringify(newQuantity) + ' updaterecord' + JSON.stringify(event.updaterecord.quantity) );
var payload = {
qryString : event.qryString,
record: event.updaterecord,
dbConfigPool : event.dbConfigPool
}
console.log('update' + JSON.stringify(payload));
var params = {
FunctionName: 'updateInventory',
Payload: JSON.stringify(payload)
}
console.log(' before invoke ' + JSON.stringify(params) )
lambda.invoke(params, function(err, data) {
console.log(' after invoke ' + JSON.stringify(params) )
if (err) {
console.log('err ' + err, err.stack); // an error occurred
event.message = err + ' query error';
} else {
console.log(data);
} // else
}); // lambda invoke
}
} // successful response
});
console.log(' end of function');
var completed = true;
context.callbackWaitsForEmptyEventLoop = false;
callback(null, completed);
}
Apologies if the code is quite long. But I wanted to show that I did put a number of console.logs to monitor where is goes through. The cloudwatch logs only shows the first message before the first lambda.invoke and then it shows the last message for the end of the function.
I am also not seeing any log entries in cloudwatch for the three functions that has been invoked.
06/17
ok, since I am still unable to make this work, I simplified the code to just the following:
exports.handler = (event, context, callback) => {
var err = null;
var updatedRecord = false;
var responseDetail = {};
var payload = {
qryString : event.qryString,
record: event.updaterecord,
dbConfigPool : event.dbConfigPool
}
var params = {
FunctionName: 'getInventory',
Payload: JSON.stringify(payload)
}
console.log(' before invoke ' + JSON.stringify(params));
lambda.invoke(params, function(err, data) {
if (err) {
event.message = err + ' query error';
callback(err,event.message);
}
else {
console.log('success' + JSON.stringify(data));
console.log(' status code ' + JSON.stringify(data.StatusCode));
console.log(' Payload ' + JSON.stringify(JSON.parse(data.Payload)));
callback(null, data);
} // successful response
});
console.log(' end of function');
// var completed = true;
// context.callbackWaitsForEmptyEventLoop = false;
// callback(null, completed);
}
However, the function is timing out when I do my test. I have also given the role attached to the functions full Lambda and RDS access.
First of all - welcome to callback hell! I will return to this later.
This is a simple code that invokes a lambda function.
var params = {
FunctionName: 'LAMBDA_FUNCTION_NAME', /* required */
};
lambda.invoke(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log(data); // successful response
}
});
lambda.invoke function has two parameters (params, function(err,data){..}). The first one is a simple JSON object. The second one is a function - a callback function. This function will be "called back" when the execution of lambda.invoke (you could think LAMBDA_FUNCTION_NAME) ends. If an error occurs it would be "stored" in err variable otherwise returned data will be stored in data variable (This is not the right explanation but I am trying to keep it simple here).
What happens when you want to invoke two lambda functions one after another?
var params1 = {
FunctionName: 'LAMBDA_FUNCTION_1_NAME', /* required */
};
lambda.invoke(params1, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('Lambda function 1 invoked!');
console.log(data); // successful response
}
});
var params2 = {
FunctionName: 'LAMBDA_FUNCTION_2_NAME', /* required */
};
lambda.invoke(params2, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('Lambda function 2 invoked!');
console.log(data); // successful response
}
});
console.log('I am done!');
Depending on execution time of LAMBDA_FUNCTION_1_NAME and LAMBDA_FUNCTION_2_NAME you could see different outputs like:
Lambda function 1 invoked!
I am done!
or
Lambda function 1 invoked!
Lambda function 2 invoked!
I am done!
or even
Lambda function 1 invoked!
I am done!
Lambda function 2 invoked!
This is because you are calling lambda.invoke and after that (without waiting) you are calling lambda.invoke again. After that (of course without waiting) the previous functions to end you are calling console.log('I am done!');
You could solve this by putting each function in previous function's callback. Something like this:
var params1 = {
FunctionName: 'LAMBDA_FUNCTION_1_NAME', /* required */
};
lambda.invoke(params1, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('Lambda function 1 invoked!');
console.log(data); // successful response
var params2 = {
FunctionName: 'LAMBDA_FUNCTION_2_NAME', /* required */
};
lambda.invoke(params2, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('Lambda function 2 invoked!');
console.log(data); // successful response
console.log('I am done!');
}
});
}
});
That way your output would be:
Lambda function 1 invoked!
Lambda function 2 invoked!
I am done!
But if you want to invoke 3 or more functions one after another you will end up with nested code. This is the callback hell. You could re-write you code in that way. But in my opinion it is a good idea to check waterfall async library
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
})
Pseudo code should look like this:
async.waterfall([
function(callback1) {
var params1 = {
FunctionName: 'LAMBDA_FUNCTION_1_NAME', /* required */
};
lambda.invoke(params1, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('LAMBDA_FUNCTION_1_NAME finished!');
callback1(null,data);
}
});
},
function(result_from_function_1, callback2) {
console.log(result_from_function_1); // outputs result from LAMBDA_FUNCTION_1_NAME
var params2 = {
FunctionName: 'LAMBDA_FUNCTION_2_NAME', /* required */
};
lambda.invoke(params2, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('LAMBDA_FUNCTION_2_NAME finished!');
callback2(null,data);
}
});
},
function(result_from_function_2, callback3) {
console.log(result_from_function_2); // outputs result from LAMBDA_FUNCTION_2_NAME
var params3 = {
FunctionName: 'LAMBDA_FUNCTION_3_NAME', /* required */
};
lambda.invoke(params3, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log('LAMBDA_FUNCTION_3_NAME finished!');
callback3(null,data);
}
});
}
], function (err, result) {
// result now equals LAMBDA_FUNCTION_3_NAME result
})
Please note that all callbacks (callback1, callback2 and callback3) could be with name "callback" only. I changed their names for better understanding.
Think about what this does:
lambda.invoke(params, function(err, data) { ...
It starts "doing something" (the fact that happens to be invoking another lambda function is actually unimportant) and when "something" is done, it calls function(), right?
But it also returns immediately, and the next statement executes.
Meanwhile "something" is being handled by the asynchronous event loop.
The "next statement" is
console.log(' end of function');
Then, you're telling lambda "hey, I may have some async stuff going on, but don't worry about waiting for it to finish":
context.callbackWaitsForEmptyEventLoop = false;
So the good news is your code is doing what is was written to do... but that turns out to be wrong.
Everywhere you have one of these two things...
// an error occurred
// successful response
...those are the places you should be calling callback(), not at the end of the handler function, which your code reaches very quickly, as it is supposed to.
You shouldn't need to use context.callbackWaitsForEmptyEventLoop = false; if you properly disconnect your database connections and all modules you include are well-behaved. While that has its purposes, there seem to be a lot of people using it to cover up for subtle unfinished business.
Or, for a tidier solution, where you only have one mention of the callback at the end and your function { function { function { nesting doesn't get so out of control, use async-waterfall.
I have a python background and is currently migrating to node.js. I have problem adjusting to node.js due to its asynchronous nature.
For example, I am trying to return a value from a MySQL function.
function getLastRecord(name)
{
var connection = getMySQL_connection();
var query_str =
"SELECT name, " +
"FROM records " +
"WHERE (name = ?) " +
"LIMIT 1 ";
var query_var = [name];
var query = connection.query(query_str, query_var, function (err, rows, fields) {
//if (err) throw err;
if (err) {
//throw err;
console.log(err);
logger.info(err);
}
else {
//console.log(rows);
return rows;
}
}); //var query = connection.query(query_str, function (err, rows, fields) {
}
var rows = getLastRecord('name_record');
console.log(rows);
After some reading up, I realize the above code cannot work and I need to return a promise due to node.js's asynchronous nature. I cannot write node.js code like python. How do I convert getLastRecord() to return a promise and how do I handle the returned value?
In fact, what I want to do is something like this;
if (getLastRecord() > 20)
{
console.log("action");
}
How can this be done in node.js in a readable way?
I would like to see how promises can be implemented in this case using bluebird.
This is gonna be a little scattered, forgive me.
First, assuming this code uses the mysql driver API correctly, here's one way you could wrap it to work with a native promise:
function getLastRecord(name)
{
return new Promise(function(resolve, reject) {
// The Promise constructor should catch any errors thrown on
// this tick. Alternately, try/catch and reject(err) on catch.
var connection = getMySQL_connection();
var query_str =
"SELECT name, " +
"FROM records " +
"WHERE (name = ?) " +
"LIMIT 1 ";
var query_var = [name];
connection.query(query_str, query_var, function (err, rows, fields) {
// Call reject on error states,
// call resolve with results
if (err) {
return reject(err);
}
resolve(rows);
});
});
}
getLastRecord('name_record').then(function(rows) {
// now you have your rows, you can see if there are <20 of them
}).catch((err) => setImmediate(() => { throw err; })); // Throw async to escape the promise chain
So one thing: You still have callbacks. Callbacks are just functions that you hand to something to call at some point in the future with arguments of its choosing. So the function arguments in xs.map(fn), the (err, result) functions seen in node and the promise result and error handlers are all callbacks. This is somewhat confused by people referring to a specific kind of callback as "callbacks," the ones of (err, result) used in node core in what's called "continuation-passing style", sometimes called "nodebacks" by people that don't really like them.
For now, at least (async/await is coming eventually), you're pretty much stuck with callbacks, regardless of whether you adopt promises or not.
Also, I'll note that promises aren't immediately, obviously helpful here, as you still have a callback. Promises only really shine when you combine them with Promise.all and promise accumulators a la Array.prototype.reduce. But they do shine sometimes, and they are worth learning.
I have modified your code to use Q(NPM module) promises.
I Assumed your 'getLastRecord()' function that you specified in above snippet works correctly.
You can refer following link to get hold of Q module
Click here : Q documentation
var q = require('q');
function getLastRecord(name)
{
var deferred = q.defer(); // Use Q
var connection = getMySQL_connection();
var query_str =
"SELECT name, " +
"FROM records " +
"WHERE (name = ?) " +
"LIMIT 1 ";
var query_var = [name];
var query = connection.query(query_str, query_var, function (err, rows, fields) {
//if (err) throw err;
if (err) {
//throw err;
deferred.reject(err);
}
else {
//console.log(rows);
deferred.resolve(rows);
}
}); //var query = connection.query(query_str, function (err, rows, fields) {
return deferred.promise;
}
// Call the method like this
getLastRecord('name_record')
.then(function(rows){
// This function get called, when success
console.log(rows);
},function(error){
// This function get called, when error
console.log(error);
});
I am new to Node.js and promises. I was searching for a while for something that will meet my needs and this is what I ended up using after combining several examples I found. I wanted the ability to acquire connection per query and release it right after the query finishes (querySql), or to get a connection from pool and use it within Promise.using scope, or release it whenever I would like it (getSqlConnection).
Using this method you can concat several queries one after another without nesting them.
db.js
var mysql = require('mysql');
var Promise = require("bluebird");
Promise.promisifyAll(mysql);
Promise.promisifyAll(require("mysql/lib/Connection").prototype);
Promise.promisifyAll(require("mysql/lib/Pool").prototype);
var pool = mysql.createPool({
host: 'my_aws_host',
port: '3306',
user: 'my_user',
password: 'my_password',
database: 'db_name'
});
function getSqlConnection() {
return pool.getConnectionAsync().disposer(function (connection) {
console.log("Releasing connection back to pool")
connection.release();
});
}
function querySql (query, params) {
return Promise.using(getSqlConnection(), function (connection) {
console.log("Got connection from pool");
if (typeof params !== 'undefined'){
return connection.queryAsync(query, params);
} else {
return connection.queryAsync(query);
}
});
};
module.exports = {
getSqlConnection : getSqlConnection,
querySql : querySql
};
usage_route.js
var express = require('express');
var router = express.Router();
var dateFormat = require('dateformat');
var db = require('../my_modules/db');
var getSqlConnection = db.getSqlConnection;
var querySql = db.querySql;
var Promise = require("bluebird");
function retrieveUser(token) {
var userQuery = "select id, email from users where token = ?";
return querySql(userQuery, [token])
.then(function(rows){
if (rows.length == 0) {
return Promise.reject("did not find user");
}
var user = rows[0];
return user;
});
}
router.post('/', function (req, res, next) {
Promise.resolve().then(function () {
return retrieveUser(req.body.token);
})
.then(function (user){
email = user.email;
res.status(200).json({ "code": 0, "message": "success", "email": email});
})
.catch(function (err) {
console.error("got error: " + err);
if (err instanceof Error) {
res.status(400).send("General error");
} else {
res.status(200).json({ "code": 1000, "message": err });
}
});
});
module.exports = router;
I am still a bit new to node, so maybe I missed something let me know how it works out. Instead of triggering async node just forces it on you, so you have to think ahead and plan it.
const mysql = require('mysql');
const db = mysql.createConnection({
host: 'localhost',
user: 'user', password: 'password',
database: 'database',
});
db.connect((err) => {
// you should probably add reject instead of throwing error
// reject(new Error());
if(err){throw err;}
console.log('Mysql: Connected');
});
db.promise = (sql) => {
return new Promise((resolve, reject) => {
db.query(sql, (err, result) => {
if(err){reject(new Error());}
else{resolve(result);}
});
});
};
Here I am using the mysql module like normal, but instead I created a new function to handle the promise ahead of time, by adding it to the db const. (you see this as "connection" in a lot of node examples.
Now lets call a mysql query using the promise.
db.promise("SELECT * FROM users WHERE username='john doe' LIMIT 1;")
.then((result)=>{
console.log(result);
}).catch((err)=>{
console.log(err);
});
What I have found this useful for is when you need to do a second query based on the first query.
db.promise("SELECT * FROM users WHERE username='john doe' LIMIT 1;")
.then((result)=>{
console.log(result);
var sql = "SELECT * FROM friends WHERE username='";
sql = result[0];
sql = "';"
return db.promise(sql);
}).then((result)=>{
console.log(result);
}).catch((err)=>{
console.log(err);
});
You should actually use the mysql variables, but this should at least give you an example of using promises with mysql module.
Also with above you can still continue to use the db.query the normal way anytime within these promises, they just work like normal.
Hope this helps with the triangle of death.
You don't need to use promises, you can use a callback function, something like that:
function getLastRecord(name, next)
{
var connection = getMySQL_connection();
var query_str =
"SELECT name, " +
"FROM records " +
"LIMIT 1 ";
var query_var = [name];
var query = connection.query(query_str, query_var, function (err, rows, fields) {
//if (err) throw err;
if (err) {
//throw err;
console.log(err);
logger.info(err);
next(err);
}
else {
//console.log(rows);
next(null, rows);
}
}); //var query = connection.query(query_str, function (err, rows, fields) {
}
getLastRecord('name_record', function(err, data) {
if(err) {
// handle the error
} else {
// handle your data
}
});
Using the package promise-mysql the logic would be to chain promises using then(function(response){your code})
and
catch(function(response){your code}) to catch errors from the "then" blocks preceeding the catch block.
Following this logic, you will pass query results in objects or arrays using return at the end of the block. The return will help passing the query results to the next block. Then, the result will be found in the function argument (here it is test1). Using this logic you can chain several MySql queries and the code that is required to manipulate the result and do whatever you want.
the Connection object is created to be global because every object and variable created in every block are only local. Don't forget that you can chain more "then" blocks.
var config = {
host : 'host',
user : 'user',
password : 'pass',
database : 'database',
};
var mysql = require('promise-mysql');
var connection;
let thename =""; // which can also be an argument if you embed this code in a function
mysql.createConnection(config
).then(function(conn){
connection = conn;
let test = connection.query('select name from records WHERE name=? LIMIT 1',[thename]);
return test;
}).then(function(test1){
console.log("test1"+JSON.stringify(test1)); // result of previous block
var result = connection.query('select * from users'); // A second query if you want
connection.end();
connection = {};
return result;
}).catch(function(error){
if (connection && connection.end) connection.end();
//logs out the error from the previous block (if there is any issue add a second catch behind this one)
console.log(error);
});
To answer your initial question: How can this be done in node.js in a readable way?
There is a library called co, which gives you the possibility to write async code in a synchronous workflow. Just have a look and npm install co.
The problem you face very often with that approach, is, that you do not get Promise back from all the libraries you like to use. So you have either wrap it yourself (see answer from #Joshua Holbrook) or look for a wrapper (for example: npm install mysql-promise)
(Btw: its on the roadmap for ES7 to have native support for this type of workflow with the keywords async await, but its not yet in node: node feature list.)
This can be achieved quite simply, for example with bluebird, as you asked:
var Promise = require('bluebird');
function getLastRecord(name)
{
return new Promise(function(resolve, reject){
var connection = getMySQL_connection();
var query_str =
"SELECT name, " +
"FROM records " +
"WHERE (name = ?) " +
"LIMIT 1 ";
var query_var = [name];
var query = connection.query(query_str, query_var, function (err, rows, fields) {
//if (err) throw err;
if (err) {
//throw err;
console.log(err);
logger.info(err);
reject(err);
}
else {
resolve(rows);
//console.log(rows);
}
}); //var query = connection.query(query_str, function (err, rows, fields) {
});
}
getLastRecord('name_record')
.then(function(rows){
if (rows > 20) {
console.log("action");
}
})
.error(function(e){console.log("Error handler " + e)})
.catch(function(e){console.log("Catch handler " + e)});
May be helpful for others, extending #Dillon Burnett answer
Using async/await and params
db.promise = (sql, params) => {
return new Promise((resolve, reject) => {
db.query(sql,params, (err, result) => {
if(err){reject(new Error());}
else{resolve(result);}
});
});
};
module.exports = db;
async connection(){
const result = await db.promise("SELECT * FROM users WHERE username=?",[username]);
return result;
}
I'm new to Node.js. I have a function 'getFromDb' that accesses a mysql database and returns a json file with some data. What if I have an array of query data and I want to call the same function through a for loop to get a json file for each element of the array?
var http = require('http');
http.createServer(function(req, res) {
console.log('Receving request...');
var callback = function(err, result) {
res.setHeader('Content-disposition', 'attachment; filename=' + queryData+ '.json');
res.writeHead(200, {
'Content-Type' : 'x-application/json'
});
console.log('json:', result);
res.end(result);
};
getFromDb(callback, queryData);}
).listen(9999);
function getFromDb(callback, queryData){
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'xxxx',
password : 'xxxx',
database : 'xxxx',
port: 3306
});
connection.connect();
var json = '';
var data = queryData + '%';
var query = 'SELECT * FROM TABLE WHERE POSTCODE LIKE "' + data + '"';
connection.query(query, function(err, results, fields) {
if (err)
return callback(err, null);
console.log('The query-result is: ', results);
// wrap result-set as json
json = JSON.stringify(results);
/***************
* Correction 2: Nest the callback correctly!
***************/
connection.end();
console.log('JSON-result:', json);
callback(null, json);
});
}
You could use the async library for node for this. That library has many functions that make asynchronous programming in NodeJS much easier. The "each" or "eachSeries" functions would work. "each" would make all the calls to mysql at once time, while "eachSeries" would wait for the previous call to finish. You could use that inside your getFromDB method for your array.
See:
https://github.com/caolan/async#each
var http = require('http'),
async = require('async');
http.createServer(function(req, res) {
console.log('Receving request...');
var callback = function(err, result) {
res.setHeader('Content-disposition', 'attachment; filename=' + queryData+ '.json');
res.writeHead(200, {
'Content-Type' : 'x-application/json'
});
console.log('json:', result);
res.end(result);
};
getFromDb(callback, queryData);}
).listen(9999);
function getFromDb(callback, queryData){
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'xxxx',
password : 'xxxx',
database : 'xxxx',
port: 3306
});
connection.connect();
var arrayOfQueryData = ["query1", "query2", "query3", "query4", "query5"];
var jsonResults = [];
async.each(arrayOfQueryData, function (queryData, cb) {
var data = queryData + '%';
var query = 'SELECT * FROM TABLE WHERE POSTCODE LIKE "' + data + '"';
connection.query(query, function(err, results, fields) {
if (err)
return cb(err);
console.log('The query-result is: ', results);
// wrap result-set as json
var json = JSON.stringify(results);
console.log('JSON-result:', json);
jsonResults.push(json);
cb();
});
}, function (err) {
connection.end();
// callbacks from getFromDb
if (err) {
callback(err);
}
else {
callback(null,jsonResults);
}
});
}
use async module. it is the best one. If u dont want to add new module try following;
var count = 0;
array.forEach(function(element) { //array of the data that is to be used to call mysql
++count; //increase counter for each service call
async.db.call(element, callback); //the async task
}
var data = [];
function callback(err, resp) {
--count;//subtract for each completion
data.push(resp)
if(count == 0) { //return data when all is complete
return data;
}
}
I would recommend the async module though. it is very good practice and useful.
I have a db module in my node worker. All I need is make it straight, without callbacks. query method still returns undefined. I suppose I did something very wrong in this piece of code but I have never seen the full implementation of promises in one place.
db: {
init: function (parent) {
var self = this;
self.parent = parent;
self.pool = mysql.createPool({
host: self.parent.cfg['mysql']['host'].replace(/"/g, ''),
user: self.parent.cfg['mysql']['username'].replace(/"/g, ''),
port: self.parent.cfg['mysql']['port'].replace(/"/g, ''),
password: self.parent.cfg['mysql']['password'].replace(/"/g, ''),
database: self.parent.cfg['mysql']['db'].replace(/"/g, '')
});
self._query = Q.nbind(self.__query, self);
},
query: function (query, params) {
var self = this;
return self._query(query, params).then(function (rows) {
return rows;
}).catch(function(err){
return err;
}).done();
},
__query: function (query, params) {
var self = this, deferred;
deferred = Q.defer();
query = params ? mysql.format(query, params) : query;
//console.log(query);
self.pool.getConnection(function (err, connection) {
if (err) {
deferred.reject(err);
}
connection.query(query, function (err, rows) {
connection.release();
if (err) {
deferred.reject(err);
}
deferred.resolve(rows);
});
});
return deferred.promise;
}
}
dump of deferred.promise
Most likely that Q.nbind is not necessary here, anyway I dumped everything and deferred.promise is an object {state: 'pending'}. So my opinion the devil is in __query function.
added
query: function (query, params) {
var self = this;
return self._query(query, params).then(function (rows) {
console.log(rows);
return rows;
}).catch(function(err){
return err;
}).done();
}
it puts rows to console, but doesn't return it. One guy claimed that such structure works:
function getKey(key) {
return r.get(dbkey(key)).then(function(x) {
return x && parseInt(x, 10);
});
}
added again
Yes, removing done() makes sense. But it still returns {state: 'pending'}. May be the problem is in the way I am calling query?
test: function () {
var self = this;
var s = self.db.query(self.testQuery);
console.log(s); // { state: 'pending' }
},
I believe you problem is how you are using Q.nbind. It is intended for functions that make a node style callback function(err, result ...
You are dealing with this in your __query function with the deferred. So I think you should just use __query directly and you will be in business. I'll post a code example when I'm back on my computer.
Ok, solution
db: {
init: function (parent) {
var self = this;
self.parent = parent;
self.pool = mysql.createPool({
host: self.parent.cfg['mysql']['host'].replace(/"/g, ''),
user: self.parent.cfg['mysql']['username'].replace(/"/g, ''),
port: self.parent.cfg['mysql']['port'].replace(/"/g, ''),
password: self.parent.cfg['mysql']['password'].replace(/"/g, ''),
database: self.parent.cfg['mysql']['db'].replace(/"/g, '')
});
},
query: function (query, params) {
var self = this;
return self._query(query, params).then(function (rows) {
return rows;
}).catch(function (err) {
return err;
});
},
_query: function (query, params) {
var self = this, deferred;
deferred = Q.defer();
query = params ? mysql.format(query, params) : query;
//console.log(query);
self.pool.getConnection(function (err, connection) {
if (err) {
deferred.reject(err);
}
connection.query(query, function (err, rows) {
connection.release();
if (err) {
deferred.reject(err);
}
deferred.resolve(rows);
});
});
return deferred.promise;
}
}
And how to call it:
test: function () {
var self = this;
Q.async(function*() {
var s = yield self.db.query(self.testsql);
console.log(s); // it is real rows
})().done();
},
That is really powerful thing, you can write methods which returns promises and then use them all in one place you want:
Q.async(function*() {
var device = yield self.modules.ami.device.get('123');
var ext = yield self.modules.ami.extension.get('123');
})().done();
While the ami.device.get and ami.extension.get method returns db.query promises:
get: function(id){
var self = this;
id = parseInt(id);
return self.top.db.query(self.select + "WHERE (`ami_device`.`id` = ?)", [id]);
}
I have to say thanks a lot to Gordon Bockus for help. This guy really saved me a day!