NodeJS/MySQL/Promises Troubles - mysql

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.

Related

convert a NodeJS lambda function (AWS) to use "async" (promises, etc.) instead of callbacks

I have a lambda function that connects to mysql and runs a set of queries, but I actually have a sequence of mysql queries that need to run one after another. I.e., the value of one query is used in the next query, etc.
Currently, I have a bunch of callbacks to achieve this, but this is leading to "callback hell". How would I rewrite this to use async / await?
My code is actually split into 2 files. The first file does an initial query, and then the value is passed into a function of the second file. Please note that the mysql node_module is included but not shown here. The AWS API gateway calls index.js
// index.js
var mysql = require('mysql'); // from node_modules
var config = require('./config.json');
var dashboard = require('./dashboard.js');
var pool = mysql.createPool({
host : config.dbhost,
user : config.dbuser,
password : config.dbpassword,
database : config.dbname
});
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
pool.getConnection(function(err, connection) {
// check for mysql connection error first
if ( err ) {
throw err;
}
let qry = "select id from some_table where some_field = ?";
let someval = event.queryStringParameters.someval;
connection.query(qry, [someval], function(error, result) {
if ( error ) {
throw err;
}
else {
dashboard.processRequest(connection, callback, event, res[0].id);
}
});
});
}
// dashboard.js
module.exports = {
jsonResponse: function(results) {
return {
"statusCode": 200,
"body": JSON.stringify({ results }),
"isBase64Encoded": false,
"headers": {
"Access-Control-Allow-Origin": "*"
}
};
},
processRequest: function(connection, callback, event, val) {
let qry = "update first_table set some_field = ?";
connection.query(qry, [val], function(error, results) {
// return to client if error
if (error) {
callback(null, this.jsonResponse(error));
}
else {
// assume that this table must be update AFTER the previous statement
qry = "select id from second_table where some_field = ?";
connection.query(qry, [val], function(error1, results1) {
// return to client if error
if ( error1 ) {
callback(null, this.jsonResponse(error1));
}
qry = "update third_table set some_field = ? where id = ?";
connection.query(qry, [results1[0].id], function(error2, results2) {
// release connection when all queries are completed
connection.release();
if ( error2 ) {
callback(null, this.jsonResponse(error2));
}
else {
callback(null, this.jsonResponse(results2));
}
});
});
}
});
}
};
It was suggested to me that something like the below code might work. Unfortunately, it does not. I was curious to know why using try...catch blocks in the way shown below is not working, and is it the same thing as what you've shown, but just written differently?
// index.js
var mysql = require('mysql'); // from node_modules
var config = require('./config.json');
var dashboard = require('./dashboard.js');
var pool = mysql.createPool({
host : config.dbhost,
user : config.dbuser,
password : config.dbpassword,
database : config.dbname
});
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
pool.getConnection(function(err, connection) {
// check for mysql connection error first
if ( err ) {
throw err;
}
let qry = "select id from users where username = ? limit 1;";
let username = event.queryStringParameters.username;
try {
let res = await connection.query(qry, [event.queryStringParameters.username]);
dashboard.processRequest(connection, callback, event, res[0].id);
} catch (err) {
console.log(err);
}
});
}
// dashboard.js
module.exports = {
jsonResponse: function (results) {
return {
"statusCode": 200,
"body": JSON.stringify({results}),
"isBase64Encoded": false,
"headers": {
"Access-Control-Allow-Origin": "*"
}
};
},
processRequest: async function (connection, callback, event, val) {
let qry = "update first_table set some_field = ?";
try {
let results = await connection.query(qry, [val]);
qry = "select id from second_table where some_field = ?";
try {
let results1 = await connection.query(qry, [val]);
qry = "update third_table set some_field = ? where id = ?";
try {
let results2 = await connection.query(qry, [results1[0].id]);
connection.release();
callback(null, this.jsonResponse(results2));
} catch (error2) {
callback(null, this.jsonResponse(error2));
}
} catch (error1) {
callback(null, this.jsonResponse(error1));
}
} catch (error) {
callback(null, this.jsonResponse(error));
}
}
};
We need use promises.
Typically I follow this approach:
Create one async method mainProcess and have bunch of methods step by step called with in that method. one after the other with await or all at once.
Each async method getConnection and runQuery in this case, called within mainProcess must a Promise.
If any errors from these methods i.e promise rejects from individual methods, goes in catch block of mainProcess().
If no errors, all methods within mainProcess gets executed and goes to then block of mainProcess()
Your code can be refactored like this (just wrote in an editor untested)
var pool = mysql.createPool({
host: config.dbhost,
user: config.dbuser,
password: config.dbpassword,
database: config.dbname,
});
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
/**
* Main Lambda Process
*/
const mainProcess = async () => {
// Get Connection
let connection = await getConnection();
// Run Step 1
let qry1 = "select id from some_table1 where some_field = ?";
const response1 = await runQuery(connection, qry1, { someFiledValue: 1222})
// Run Step 2
let qry2 = "select id from some_table2 where some_field = ?";
const resonse2 = await runQuery(connection, qry2, { someFiledValue: 1222})
return 'All Good';
});
}
mainProcess()
.then(result => {
// All lambda success messages are returned from here
callback(null, result);
})
.catch(error => {
// All lambda errors thrown from here
callback(error);
});
};
function getConnection(qry, parms) {
return new Promise((resolve, reject) => {
pool.getConnection(function (error, connection) {
if (error) {
// return to client if error
reject(error);
} else {
// Return response
resolve(connection);
}
});
});
}
/**
* Runs a query, either resolves or rejects
*/
function runQuery(connection, qry, parms) {
return new Promise((resolve, reject) => {
connection.query(qry, [val], function (error, results) {
if (error) {
// return to client if error
reject(error);
} else {
// Return response
resolve(result);
}
});
});
}
When you're dealing with a lambda function which performs an async task you have two solutions:
you can use non async handlers, in which case you need to invoke "callback" on promises as you did in your example
you can use async handlers, which does not requires the "callback" input and that allows you to write async/await code, like the following example:
const mysql = require('mysql2/promise');
exports.handler = async(event, context) => {
//get path variable
const { pathVar } = event.pathParameters;
// get connection
const connection = await mysql.createConnection({
host : process.env.RDS_HOSTNAME,
user : process.env.RDS_USERNAME,
password : process.env.RDS_PASSWORD,
database : process.env.RDS_DB_NAME
});
// get text query
const textQuery = `SELECT field FROM entity WHERE attribute = ${pathVar}`;
// get res
const results = await connection.execute(textQuery);
return {
"statusCode": 200,
"body": results,
"isBase64Encoded": false
}
}
You can have a look at the AWS docs: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html

Cannot enqueue Query after invoking quit when nesting promises

I am trying to iterate through a MySQL query result and make subsequent queries in order to build out my data model. Each object requires multiple queries, therefore I am chaining promises.
The problem occurs when I nest a second set of promises.
So first I am getting a list of the objects that need to be retrieved using g.getSnapshotIds. Then I iterate through those and use the snapshotId to retrieve a full snapshot.
var gData = {};
g.getSnapshotIds(data.gId, data.userId)
.then(function(value) {
gData = value;
for ( var snapshot in value ) {
var snapshotId = value[snapshot].snapshotId;
var snapshot = {};
g.getSnapshotFull(snapshotId)
.then(function(value) {
console.log(value);
return g.getTs(snapshotId);
})
.then(function(value) {
for ( var te in value ) {
var name = value[te].t;
snapshot[name] = value[te].value;
}
console.log(snapshot);
})
.catch(function(err) {
console.log('Error:', err);
});
}
g.close();
})
.catch(function(err) {
console.log('Error:', err);
});
I am able to call g.getSnapshotFull on each ID, but when I try to move on to the next query (g.getTs(snapshotId)) it gives me the error:
Error: Cannot enqueue Query after invoking quit.
I have no idea why the MySQL connection is closing before all queries are done. Shouldn't everything inside the for loop execute sequentially before moving on?
If I comment out g.close(), I don't get the error, but the process doesn't end.
These are the relevant query methods:
class gDB {
close() {
return new Promise(function(resolve, reject) {
db.end(function(error) {
if ( error ){
reject(new Error(error));
}
// For some reason it is necessary to reestablish this
db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
ssl: {
ca: fs.readFileSync(__dirname + '/' + process.env.DBCA)
}
});
resolve(true);
});
});
}
getSnapshotIds(gId, uId) {
return new Promise(function(resolve, reject) {
var sql = 'SELECT id AS snapshotId FROM snapshots WHERE gId=' + db.escape(gId) + ' AND uId=' + db.escape(uId) + ' ORDER BY timestamp DESC';
db.query(sql, function (error, results, fields) {
if (error) {
db.destroy();
reject(new Error(error));
} else {
resolve(results);
}
});
});
}
getSnapshotFull(snapshotId) {
return new Promise(function(resolve, reject) {
var sql = 'SELECT s.id AS snapshotId, s.timestamp, s.gId, s.uId, s.clientId FROM snapshots s INNER JOIN controls c ON s.id = c.snapshotId INNER JOIN weathers w ON s.id = w.snapshotId WHERE s.id=' + db.escape(snapshotId);
db.query(sql, function (error, results, fields) {
if (error) {
db.destroy();
reject(new Error(error));
} else {
resolve(results[0]);
}
});
});
}
getTs(snapshotId) {
return new Promise(function(resolve, reject) {
var sql = 'SELECT t.t, st.value FROM snapshots s LEFT JOIN snapshot_t st ON s.id = st.snapshotId INNER JOIN ts t ON st.tId = t.id WHERE s.id=' + db.escape(snapshotId);
db.query(sql, function (error, results, fields) {
if (error) {
db.destroy();
reject(new Error(error));
} else {
resolve(results);
}
});
});
}
The problem you are having is for loops are synchronous while promises are asynchronous. What is going on is you are creating a bunch of promises that are waiting for something to happen (the promise to receive data), then the for loop ends (before any of the promises finish) and you then call close. What you'll want to do is something similar to the below.
var gData = {};
g.getSnapshotIds(data.gId, data.userId)
.then(function (value) {
gData = value;
var promises = [];
for (var snapshot in value) {
var snapshotId = value[snapshot].snapshotId;
var snapshot = {};
var promise = g.getSnapshotFull(snapshotId)
.then(function (value) {
console.log(value);
return g.getTs(snapshotId);
})
.then(function (value) {
for (var te in value) {
var name = value[te].t;
snapshot[name] = value[te].value;
}
console.log(snapshot);
});
promises.push(promise);
}
return Promise.all(promises);
})
.then(function (values) {
g.close();
console.log(values);
})
.catch(function (err) {
console.log('Error:', err);
});
What solves this is saving the promise and then using Promise.all(promises) to wait for all the promises to finish. The last then block will have the results of all of the promises and that is where you can close your database connection.

nodejs mysql queries showing only one records instead of all records in the database

Am trying to retrieve all the database records from a table called post using node js but the problem is that only one record is retrieved instead of all.
In php I can use while() loop to loop through the database record to get all data.
Currently, I do not know how to neatly loop through the database in nodejs to get all the records from database. Some Stackoverflow scholars suggest using await/async method but i do not know to to implement it on the code below to make it work. can someone help me fix the issue.
var connection = require('./config');
module.exports.getpost = function (req, res) {
connection.query('SELECT * FROM posts', function (error, results, fields) {
if (error) {
console.log('error');
res.json({
status : false,
message : 'there are some error with the query'
});
} else {
var postid = results[0].id;
var title = results[0].title;
var content = results[0].content;
var type = -1;
console.log(title);
// Checking user status
connection.query('SELECT count(*) as cntStatus,type FROM like_table WHERE userid= ? and postid=?', [userid,postid], function (error, results, fields) {
if (error) {
console.log('error');
res.json({
status : false,
message : 'there are some error with the query'
});
} else {
var total_count = results[0].cntStatus;
if(total_count > 0){
type = results[0].type;
}
var total_count = results[0].cntStatus;
var result = {
"id" : postid,
"title" : title,
"content" : content,
"type" : type,
"likes" : total_count
};
console.log('query okay');
res.json({
//data:results,
data : result
});
}
});
}
});
}
I'm assuming you're using mysql npm. In that case I'm not sure what is the problem in your case. Results param is an array of rows returned by your select statement. So you can use loop to iterate trough all the rows.
You don't actually need to use async/await (which doesn't have any advantage in terms of functionality but looks cleaner). But if you want to get rid of callbacks you need to wrap connection query into a promise or use mysql2 npm which has promise interface. Here is how you can iterate trough all the rows from your select using async/await instead of callback:
var connection = require('./config');
module.exports.getpost = async function (req, res) {
try {
const queryResult = await query('SELECT * FROM posts');
queryResult.forEach(row => {
console.log(row.title);
})
} catch (err) {
console.log('error');
res.json({
status: false,
message: 'there are some error with the query'
});
}
}
Please note that you need to use nodejs 8 to run the code with async/await.
Also you don't need to do another query inside of your posts query, you can merge those two using SQL join
async waterfall - Runs an array of functions in series, each passing their results to the next in the array. However, if any of the functions pass an error to the callback, the next function is not executed and the main callback is immediately called with the error.
var connection = require('./config');
var async = require('async');
module.exports.getpost = function (req, res) {
var arrayOfFuncs = [];
var func_1 = function(callback) {
connection.query('SELECT * FROM posts', function (error, results, fields) {
if (error) {
console.log('error');
callback(error, null);
} else {
var toPass = {};
toPass.postid = results[0].id;
toPass.title = results[0].title;
toPass.content = results[0].content;
toPass.type = -1;
callback(null, toPass);
}
})
}
arrayOfFuncs.push(func_1);
var func_2 = function(prevData, callback) {
connection.query('SELECT count(*) as cntStatus,type FROM like_table WHERE userid= ? and postid=?', [userid,prevData.postid], function (error, results, fields) {
if (error) {
console.log('error');
callback(error, null);
} else {
var total_count = results[0].cntStatus;
if(total_count > 0){
type = results[0].type;
}
var total_count = results[0].cntStatus;
var result = {
"id" : postid,
"title" : title,
"content" : content,
"type" : type,
"likes" : total_count
};
console.log('query okay');
callback(null, result);
}
});
}
arrayOfFuncs.push(func_2);
async.waterfall(arrayOfFuncs, function(errString, finalResult) {
if(errString) {
return res.send(errString);
} else {
return res.send(finalResult);
}
});
}

promise returns undefined while calling from two different mysql queries

I Have two mysql queries that runs with promise.
The first one is updates information on a mysql table and then resolves the issue and calls the next mysql query. The problem is that, when it calls the next mysql query the promise returns UNDEFINED and I am not sure why. When I console.log it out in my node js server post request, it gives undefined. I documented on the code which areas are problems.
UpdateUserPath = (data) => new Promise((resolve,reject)=>{
data.UPDATE_DT = getDateTime();
db.query('UPDATE path UPDATE_DT = ? where Owner = ?',
[data.UPDATE_DT, data.Owner], function(err,results,fields){
if(err){
reject('Could not update user path');
}else{
if(results.affectedRows > 0){
data.ID = null;
data.UPDATE_DT = null;
// The problem is here, when this gets resolved it calls the other function SaveUserPath
resolve(saveUserPath(data));
}else{
reject('Could not update user path');
}
}
});
});
saveUserPath = (data) => new Promise((resolve, reject) => {
db.query('INSERT INTO path SET ?', data, function (error, results, fields) {
if (error) {
reject('Could not insert path');
}else{
var Id = results.insertId;
db.query('UPDATE path SET ORIG_ID = ? where ID = ?',[Id, Id], function(err,results,fields){
if(err){
reject('Could not insert row to path table - saveuserpath');
}else{
if(results.affectedRows > 0){
// THIS INFORMATION HERE IS UNDEFINED
return resolve(results[0]);
}else{
reject('Could not update path');
}
}
});
}
});
});
In the server it gets called like this.
getUserPath(req.session.userid).then((path_data)=>{
path_data.status = 1;
UpdateUserPath(path_data).then((result)=>{
console.log(result); // THIS IS UNDEFINED
});
});
I am wondering if resolve(saveUserPath(data)); is the right way to call another promise which is not outside in the server.
I was thinking of just doing it this way.
UpdateUserPath(path_data).then((result)=>{
saveUserPath(result).then((result_save) => {
console.log(result_save); // THIS MIGHT WORK
});
});
But why is the normal way wrong.
I have several guesses why it isn't working, but there are a number of things wrong such that it's better to just clean up the code to a much better design.
When combining multiple asynchronous callback-driven operations in an otherwise promise-based interface, you really want to promisify the underlying functions at their lowest level and then you can implement all your control flow and error handling using the benefits of promises. I think that will also make your problem go away and probably fix a couple other bugs too.
// promisify db.query()
// if a promisified interface is built into your database, use that one instead
db.queryP = function(q, d) {
return new Promise((resolve, reject) {
db.query(q, d, (err, results, fields) => {
if (err) {
reject(err);
} else {
resolve(results);
}
});
});
}
UpdateUserPath = function(data) {
data.UPDATE_DT = getDateTime();
let q = 'UPDATE path UPDATE_DT = ? where Owner = ?';
return db.queryP(q, [data.UPDATE_DT, data.Owner]).then(results => {
if (results.affectedRows > 0) {
data.ID = null;
data.UPDATE_DT = null;
return saveUserPath(data);
} else {
throw new Error('Could not update user path');
}
});
}
saveUserPath = function(data) {
let q = 'INSERT INTO path SET ?'
return db.queryP(q, data).then(results => {
let q2 = 'UPDATE path SET ORIG_ID = ? where ID = ?';
var Id = results.insertId;
return db.queryP(q2, [Id, Id]).then(results2 => {
if (results2.affectedRows > 0) {
return results2[0];
} else {
throw new Error('Could not update path');
}
});
});
}
getUserPath(req.session.userid).then(path_data => {
path_data.status = 1;
return UpdateUserPath(path_data);
}).then(result => {
// process result here
}).catch(err => {
// process error here
});

Use promise to process MySQL return value in node.js

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;
}