Node JS callbacks in a while loop - mysql

I have a MySQL database with the table id which stores all the unique id's generated till date, using nanoid module. I have implemented the following code to generate a unique id which is not in the table.
//sql library
const mysql = require('mysql');
const sql_obj = require(__dirname + '/../secret/mysql.json');
//nanoid library
const { customAlphabet } = require('nanoid');
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const nanoid = customAlphabet(alphabet, 30);
function uniqueid(){
let found = 0;
let conn = mysql.createConnection(sql_obj);
while (found === 0){
let id = nanoid();
conn.connect(function(err){
if (err){
found = 2;
}
else{
conn.query("SELECT * FROM id WHERE value = " + mysql.escape(id),function(err,result,fields){
if (err){
found = 2;
}
else{
if (result.length === 0){
found = 1;
}
}
})
}
})
}
if (found === 2){
return {error: 1,ret: null};
}
else if (found === 1){
return {error: 0,ret: id};
}
}
console.log(uniqueid());
I knew, my implementation is wrong. Because callbacks are asynchronous in nature, the while loop never ends and hence I got the error JavaScript heap out of memory. I went through many articles in the web to sort this out, but couldn't. The main problem is that the function uniqueid should return some value, because, I am calling it from other JavaScript file.
Thanks for any help

I think best way to prevent this is using async/await.
I promisified your mySql connection. And you can send your query and values to the function.
//sql library
const mysql = require('mysql');
const sql_obj = require(__dirname + '/../secret/mysql.json');
//nanoid library
const { customAlphabet } = require('nanoid');
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const nanoid = customAlphabet(alphabet, 30);
let db = (query, values = null) => {
return new Promise((resolve, reject) => {
let conn = mysql.createConnection(sql_obj);
conn.connect(function (err) {
if (err) {
reject(err);
}
else {
conn.query(query + values, function (err, result, fields) {
if (err) {
reject(err);
return;
}
else {
if (result.length === 0) {
resolve();
}
}
})
}
})
})
}
async function uniqueid() {
while (found === 0) {
let id = nanoid();
try {
await db("SELECT * FROM id WHERE value =", mysql.escape(id));
return { error: 0, ret: id };
} catch (error) {
return { error: 1, ret: null };
}
}
}
console.log(uniqueid());

Related

How do I return an asynchronous DB query result from one module to another using Node.js?

I'm new to Node, and I'm trying to follow a pattern from a Udemy API course. The API is structured to utilize route, controller and service modules for flow. Database queries are to be run as services and they are supposed to be called from controllers.
I need to run a series of DB queries to generate a list (I'm showing only 2 of 6 queries in this example). I am running these using async/await in my function. The queries are working fine. My problem occurs when I try to return the 'batch result' (the result of all the queries) to the controller at the end of the process. I get Promise { <pending> }. I have tried many things, but I cannot end the promise to access the final result from my controller module--I can only access it from my service module.
Here is my code from my controller module (groups.controller.js) where I call my function:
const groupsService = require('../services/groups.service');
exports.propertyList = (req, res, next) => {
const uid = req.body.uid;
const batchResponse = groupsService.batchQuery(uid, res);
console.log(batchResponse);
}
And here is my code from my service module (groups.services.js) where I run the queries:
const mysql = require('mysql2');
const dbAsync = require("../config/db.config");
async function batchQuery(uid, res) {
var Q1;
var Q2;
var uid = uid * -1;
const pool = mysql.createPool(dbAsync.dbAsync);
const promisePool = pool.promise();
try {
Q1 = await promisePool.query('SELECT PropertyID FROM GroupMembership WHERE GroupID = ?', [uid]);
Q2 = await promisePool.query('SELECT SubGroupID FROM GroupMembership WHERE GroupID = ? AND PropertyID = ?', [uid, 0]);
}
catch(error) {
console.log(error);
res.status(401).send('Server error');
return error;
}
finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: '+ Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
res.status(200).send(batchResponse);
return batchResponse;
}
}
module.exports = {batchQuery};
When I send a post via postman, I get the expected query result (below). However, I can only get this to work if I put my res in my service module.
{
"Q1": [
{
"PropertyID": 0
}
],
"Q2": [
{
"SubGroupID": 397
}
]
}
Is there a way to end the promise in this pattern and return the desired batch response? Thank you.
EDIT: Adding the code updates provided by #traynor.
New controller:
const groupsService = require('../services/groups.service');
exports.propertyList = async (req, res, next) => {
const uid = req.body.uid;
let batchResponse;
try {
batchResponse = await groupsService.batchQuery(uid);
console.log(batchResponse);
return res.status(200).send(batchResponse);
} catch(error) {
console.log('Error: ' + error);
return res.status(401).send('Server error');
}
}
New service:
const mysql = require('mysql2');
const dbAsync = require("../config/db.config");
function batchQuery(uid) {
return new Promise((resolve, reject) => {
var Q1;
var Q2;
var uid = uid * -1;
const pool = mysql.createPool(dbAsync.dbAsync);
const promisePool = pool.promise();
try {
Q1 = await promisePool.query('SELECT PropertyID FROM GroupMembership WHERE GroupID = ?', [uid]);
Q2 = await promisePool.query('SELECT SubGroupID FROM GroupMembership WHERE GroupID = ? AND PropertyID = ?', [uid, 0]);
} catch(error) {
console.log(error);
reject(error);
} finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: '+ Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
resolve(batchResponse);
}
})
}
module.exports = {batchQuery};
the service is now returning a promise, and it's also handling response instead of controller.
to return from service, you need to promisify service: return a promise which resolves when you get db data, or on error, and then you also need to await the service, which it's wrapped in try/catch for error handling.
once it's all done, handle response from the controller:
service:
function batchQuery(uid) {
return new Promise(async (resolve, reject) => {
var Q1;
var Q2;
//...
try {
//...
} catch (error) {
console.log(error);
reject(error);
} finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: ' + Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
resolve(batchResponse);
}
});
controller:
exports.propertyList = async (req, res, next) => {
const uid = req.body.uid;
let batchResponse;
try {
batchResponse = await groupsService.batchQuery(uid);
console.log(batchResponse);
res.status(200).send(batchResponse);
} catch(error) {
return res.status(401).send('Server error');
}
}

Wait for MySQL query execution in node,js

I am using mysql in node.js. And there is a nested query in it. But it does not wait for inner query to complete. I don't understand call backs nd other function. My code is like.
exports._getItems=function(req,res){
let categoryId=req.query.categoryPost;
console.log(categoryId+"re")
con.query("SELECT item_id FROM shop_products where category_id =?",[categoryId], function (err, result, fields) {
if (err) throw res.status(403);
else
{
console.log("jhcsdcshd");
var parsedResult = JSON.parse(JSON.stringify(result));
let itemsData="";
for (var i = 0; i < parsedResult.length; i++)
{
let item_id=parsedResult[i].item_id;
console.log("Item_id is"+item_id)
con.query("SELECT id,modelNo FROM items where id =?",[item_id], function async(err1, result1, fields1,callback) {
if (err1) throw res.status(403);
else
{
var parsedResult1=JSON.parse(JSON.stringify(result1));
console.log(parsedResult1)
itemsData=itemsData+parsedResult1[0].id;
console.log("dddd"+itemsData);
if(i=parsedResult.length)
{
console.log("here");
console.log("Data"+itemsData)
res.status(200).send(itemsData);
}
}
});
console.log("ppppppp"+itemsData);
}
}
});
}
I just want that if anyone can tell me that exactly how i let do the wait for inner query execution according to my code . It is very appreciated if anyone can help me. Thanks
You can not call mysql query inside for loop you have to wait for execution. Here is your solutions
exports._getItems = function (req, res) {
let categoryId = req.query.categoryPost;
console.log(categoryId + "re")
con.query("SELECT item_id FROM shop_products where category_id =?", [categoryId], function (err, result, fields) {
if (err) throw res.status(403);
else {
console.log("jhcsdcshd");
var parsedResult = JSON.parse(JSON.stringify(result));
let itemsData = "";
var promises = [];
for (var i = 0; i < parsedResult.length; i++) {
let item_id = parsedResult[i].item_id;
console.log("Item_id is" + item_id);
promises.push(runQuery(con, item_id))
}
Promise.all(promises)
.then((data) => {
for (var i = 0; i < data.length; i++) {
var parsedResult1 = JSON.parse(JSON.stringify(data[i]));
console.log(parsedResult1)
itemsData = itemsData + parsedResult1[0].id;
console.log("dddd" + itemsData);
if (i = parsedResult.length) {
console.log("here");
console.log("Data" + itemsData)
res.status(200).send(itemsData);
}
}
})
.catch((error) => {
throw res.status(403);
})
}
})
}
function runQuery(con, item_id) {
return new Promise((resolve, reject) => {
con.query("SELECT id,modelNo FROM items where id =?", [item_id], function (err1, result1) {
if (err1) {
reject(err1);
} else {
resolve(JSON.parse(JSON.stringify(result1)));
}
})
});
}

Adding bulk data to mysql database

I have a console program where the user scans in serial numbers, and those serial numbers get added to a database.
const mysql = require('mysql2');
const read = require('readline-sync');
const conn = new mysql.createConnection(config);
conn.connect(
function(err){
if(err){
throw err;
}
else{
console.log("Connection Established");
while(1){
var sn = read.question('Scan in serial number: ');
conn.query('INSERT INTO test (serial) VALUES (?);',
[sn], function(err, results, fields){
if (err){
throw err;
}
else{
console.log("Added stuff");
}
});
}
}
}
);
When the code runs it successfully connects to the database but queries the database. It continually prompts for user input.
Alternatively, I tried storing serial numbers in an array and then loops through it adding each element, like this.
const mysql = require('mysql2');
const read = require('readline-sync');
var array = [];
var sn = " ";
while (1) {
sn = read.question('Scan in serial number, or enter "done" if finished scanning');
if (sn == "done") {
break;
}
array.push(sn);
}
conn.connect(
function (err) {
if (err) {
throw err;
}
else {
console.log("Connection Established");
array.forEach(function (sn) {
conn.query('INSERT INTO test (serial) VALUES (?);',
[sn], function (err, results, fields) {
if (err) {
throw err;
}
else {
console.log("Added stuff");
}
});
});
}
}
);
In this case, it works inconsistently. Sometimes it works fine, and other times it fails to connect and throws a timeout error. Is there a better way to accomplish this and/or am I doing something wrong?
var promises = []
function dbOp(value) {
return new Promise(function(resolve, reject) {
conn.query('INSERT INTO test (serial) VALUES (?);',
[value], function (err, results, fields) {
if (err) {
return reject(err)
}
else {
console.log("Added stuff");
resolve(results)
}
}
}
conn.connect(function(err){
if(err){
throw err;
}
else{
for (i = 0; i < array.length; ++i) {
promises.push(dbOp(array[i]));
}
}
});
Promise.all(promises)
.then((results) => {
console.log("done", results);
})
.catch((e) => {
console.log(e)
});
This might be caused by short idle timeout setting in your mysql server. client.connect() is pretty much a no-op in mysql2, it connects immediately when you call mysql.createConnection(). You can change order to establish connection only after all data is collected:
const mysql = require('mysql2');
const read = require('readline-sync');
var array = [];
var sn = ' ';
while (1) {
sn = read.question('Scan in serial number, or enter "done" if finished scanning');
if (sn == 'done') {
const conn = mysql.createConnection(config);
array.forEach(function(sn) {
conn.query('INSERT INTO test (serial) VALUES (?);', [sn], function(err, results, fields) {
if (err) {
throw err;
} else {
console.log('Added stuff');
}
});
});
}
array.push(sn);
}

Nodejs for loop with MySQL insert not functioning as expected

When I trying to Insert some data and process the result using "for Loop", iteration only complete in last cycle.
const sql4 = "insert into core_crm_job_task set ?";
for(var m = 0; req.body.jobTasks.length > m; m++){
console.log('a'+m);
let jobTask = {
ID_JOB_REGISTRY : registryId,
ID_TASK_CODE : jobCode+' '+req.body.jobTasks[m].task_code_suffix,
TASK_TYPE : req.body.jobTasks[m].task_type,
TASK_QTY : req.body.jobTasks[m].task_qty,
TASK_INSTRUMENT : req.body.jobTasks[m].task_instrument,
TASK_INSTRUMENT_ID : req.body.jobTasks[m].task_instrument_id,
REMARK : req.body.jobTasks[m].task_remark,
IS_ACTIVE: 1,
CREATED_BY: 1,
CREATED_DATE: getCurrentTime()
};
connection.query(sql4,jobTask, (err, result) => {
if (err){
console.log('b'+m);
connection.release();
}
else{
console.log('c'+m);
//some process here
}
})
}
Below shows sample output when I used an array with 3 elements.
The log with letter 'c' only print in last element. Other elements didn't complete the iteration.
a0
a1
a2
c2
But I need to do some process after each insert query. In this case it is impossible.
Please suggest some solution!
You can you can use async.eachAsync Ref
So it will execute one by one, you will get expected result
const sql4 = "insert into core_crm_job_task set ?";
async.each(req.body.jobTasks,function(obj,callback){
let jobTask = {
ID_JOB_REGISTRY : registryId,
ID_TASK_CODE : jobCode+' '+obj.task_code_suffix,
TASK_TYPE : obj.task_type,
};
connection.query(sql4,jobTask, (err, result) => {
if (err){
callback(err);
}
callback(null);
})
});
Problem solved! :)
Just Replaced for loop variable type to "let"
for(var m = 0; req.body.jobTasks.length > m; m++)
to
for(let m = 0; req.body.jobTasks.length > m; m++)
addEmojies: function (request, callback) {
//getting the inputs
var questions = request.body.question;
//if questions contains only one
if (!Array.isArray(questions)) {
questions = que = [request.body.question];
}
const mysqlConnection = mysqlPool(function (err, connection) {
connection.beginTransaction(function (err) {
if (err) {
connection.release();
if (err) { throw err; }
}
//loop trough the questions
for (var q = 0; q < questions.length; q++) {
//setting up data object for update category
var mainQuestionValues = {
CAMPAIGN_ID: questions[q].campaign_id,
MAIN_QUESTION_TITLE: questions[q].question,
CREATED_BY: session.userdata.user_id,
HAS_SUB_QUESTIONS:0,
IS_ACTIVE: 1
};
const sql = 'INSERT INTO main_question SET ?';
connection.query(sql, mainQuestionValues, function (err, result) {
if (err) {
if (err) { throw err; }
connection.release();
connection.rollback(function () {
return callback(false);
});
}
connection.commit(function (err) {
if (err) {
connection.release();
logger.error(err);
connection.rollback(function () {
return callback(false);
});
}
connection.release();
return callback(true);
});
});
}
});
});
},

nodejs mysql variable access after promise

I'm beginner into nodeJS world and i try to connect a mysql database. I first select a team (equipe) and after that i have to select all the members of this team.
Finally, i want to construct an array with my teams and the members (in the lesEquipes var). When the code comes to the 3rd then, my var is null ! WHY please ?
Here is my code :
class Database {
constructor() {
this.connection = mysql.createConnection({
host: "192.x.x.x",
user: "john",
password: "mypass",
database: "mybase"
});
}
query(sql, args) {
return new Promise((resolve, reject) => {
this.connection.query(sql, args, (err, result, fields) => {
if (err) return reject(err);
resolve (result);
});
});
}
close() {
return new Promise((resolve, reject) => {
this.connection.end(err => {
if (err) return reject (err);
resolve();
});
});
}
}
var database = new Database();
.............
app.post('/visu',function(req,res){
//console.log(nodedump(req.params.mdp));
//console.log(req.body);
// codage en dur de l'authentification
if((req.body.email == "john#gmail.com") && (req.body.mdp == "password")){
var leUser = "administrateur";
let lesEquipes = new Array();
let test = 'Toto';
database.query("select * from equipe order by id")
.then( result => {
for (var i = 0; i < result.length; i++) {
let courante = new Array();
let nomEquipe = result[i].nomEquipe;
let idEquipe = result[i].id;
courante[0] = result[i];
var sql2 = "select * from participant where equipe = " + result[i].id;
//console.log(sql2);
database.query(sql2)
.then(result2 => {
console.log("Membres de l'équipe : " + nomEquipe);
courante[1] = result2;
console.log("COURANTE 2 "+ JSON.stringify(courante));
lesEquipes[idEquipe] = courante;
let test = 'AUTRE CHOSE';
/*
console.log("QUERY 2 "+ JSON.stringify(result2));
for (var j = 0; j < result2.length; j++) {
participant = result2[j].nom;
console.log(participant);
}
*/
//res.render('visuEquipes.ejs', { user:leUser, equipes:lesEquipes});
//console.log("Contenu "+ JSON.stringify(lesEquipes));
return ""; //database.query(sql3);
});
}
console.log("Contenu "+ JSON.stringify(test));
console.log("Contenu "+ JSON.stringify(lesEquipes));
}).then(result3 =>{
console.log("result 3 : "+result3);
console.log("Contenu "+ JSON.stringify(test));
**// THIS VAR IS NULL NULL NULL lesEquipes
console.log("Contenu "+ JSON.stringify(lesEquipes));**
res.render('visuEquipes.ejs', { user:leUser, equipes : lesEquipes});
}).catch((err) => {
console.log(err);
database.close();
});
The problem is that you are executing asynchronous code inside a loop.
for (var i = 0; i < result.length; i++) {
/**/
database.query(sql2).then(result2 => {
/**/
});
}
So in .then(result3 =>{ the variable lesEquipes is very likely to have not been initialized.
You have to wait for all the promises to resolve. You can easly add an array of promises.
const promises = [];
for (var i = 0; i < result.length; i++) {
promises.push(database.query(sql2).then(result2 => {/*...*/}));
}
return Promise.all(promises);
Or you can use async-p library to iterate the array, or better you can use ES7 async-await constructs.