Node Js Query MySql Before Sending Notification in Firebase - mysql

Please how do I Query MySql and wait for result before sending notification to device. Here is my code
exports.pushNotification = functions.database
.ref("/messages")
.onWrite((change, context) => {
console.log("Push notification event triggered");
const payload = {
notification: {
title: "Test Message",
body: "Welcome to Node",
sound: "default"
},
data: {
title: "Test Message",
message: "Welcome to Node"
}
};
/* Create an options object that contains the time to live for the notification and the priority. */
const options = {
priority: "high",
timeToLive: 60 * 60 * 24 //24 hours
};
var token = new Promise(function(resolve, reject) {
con.connect(function(err) {
if (err) {
reject(err);
} else {
con.query("SELECT fcm_token FROM users WHERE id = 24", function(
err,
result,
fields
) {
if (err) {
reject(err);
} else {
token = result[0].fcm_token;
resolve(token);
}
});
}
});
});
console.log("Token : " + token);
return admin.messaging().sendToDevice(token, payload, options);
});
I keep getting the following errors:

According to the documentation sendToDevice does not take a promise as a parameter. So you will have to call sendToDevice with the resolved token(s) instead and only then resolve/reject.
exports.pushNotification = functions.database
.ref("/messages")
.onWrite((change, context) => {
console.log("Push notification event triggered");
...
return new Promise(function(resolve, reject) {
con.connect(function(err) {
if (err) {
reject(err);
} else {
con.query("SELECT fcm_token FROM users WHERE id = 24", function(
err,
result,
fields
) {
if (err) {
reject(err);
} else {
token = result[0].fcm_token;
console.log("Token : " + token);
admin.messaging().sendToDevice(token, payload, options).then(function() {
resolve();
}).catch(function(err) {
reject(err);
});
}
});
}
});
});
});

Related

How to wait for a MYSQL query to finish before executing another using Node server?

I am building an Express server to receive request (a dict with 10 items) from my React front end and then save the data to database. Below is my code.
I found that the query may crash during the insertion e.g. 2 queries got the same id by last_insert_id(). I have tried to use setTimeout() to wrap the getConnection function but the issue still exists. How to better solve the problem?
The request data:
{{.....}, {.....}, {.....}, {.....}, {.....}} #10 item
Code:
router.post('/fruit', (req, res) => {
const dict = req.body;
let itemCount = 0;
var err_list = [];
Object.keys(dict).forEach(function(r){
let query = "call sp_insert_fruit();"
setTimeout(function() {
getConnection(function(err, conn){
if (err) {
return res.json({ success: false, error: err })
} else {
conn.query(query, function (err, result, fields) {
if (err) {
err_list.push({'errno':err.errno, 'sql_message':err.sqlMessage});
}
itemCount ++;
if (itemCount === Object.keys(dict).length) {
conn.release()
console.log('released', err_list)
if (err_list .length === 0) {
return res.json({ success: true});
} else {
return res.json({ success: false, error: err_list});
}
}
});
}
});
}, 1000);
});
});
connection.js:
const p = mysql.createPool({
"connectionLimit" : 100,
"host": "example.org",
"user": "test",
"password": "test",
"database": "test",
"multipleStatements": true
});
const getConnection = function(callback) {
p.getConnection(function(err, connection) {
callback(err, connection)
})
};
module.exports = getConnection
You should replace callbacks with Promises and async/await to avoid callback hell. Using Promises, this problem should be easy to solve.
connection.js
const p = mysql.createPool({
"connectionLimit" : 100,
"host": "example.org",
"user": "test",
"password": "test",
"database": "test",
"multipleStatements": true
});
// wrap p.getConnection with Promise
function getConnection() {
return new Promise((resolve, reject) => {
p.getConnection((err, connection) => {
if (err) reject(err);
else resolve(connection);
});
});
};
module.exports = getConnection;
Router code
// wrap conn.query with Promise
function executeQuery(conn, query) {
return new Promise((resolve, reject) => {
conn.query(query, (err, result, fields) => {
if (err) reject(err);
else resolve({ result, fields });
});
});
}
router.post('/fruit', async (req, res) => {
const dict = req.body;
const errList = [];
const query = "call sp_insert_fruit();"
let conn = null;
try {
conn = await getConnection();
} catch (err) {
return res.json({
success: false,
error: err
});
}
for (const r of Object.keys(dict)) {
try {
const { result, fields } = await executeQuery(conn, query);
} catch (err) {
errList.push({
'errno': err.errno,
'sql_message': err.sqlMessage
});
}
}
conn.release();
console.log('released', errList);
// I don't know what err_imnt is, so I guess it's errList?
if (errList.length === 0) {
return res.json({
success: true
});
} else {
return res.json({
success: false,
error: errList
});
}
});

MySQL Transactions in Node

Before I commit anything to the database, I want all my update promises resolve; otherwise, I rollback. In other words, I want atomicity. I suppose I could handle the rollback by deleting out rows, but this has its own risks. I noticed if there is an error in any of the promises, the data still gets updated in database. What am I doing wrong?
I have written a simple program to illustrate the issue.
This is the main process:
const db = require('./db.js');
const async = require('async');
let insertList = [];
for (let i = 0; i<3; i++) {
insertList.push(i);
}
async function func1 () {
return new Promise((resolve, reject) => {
console.log("In Func1");
async.forEachOf(insertList, function(value, key, callback) {
console.log('>>>>' + value + '<<<<<<' + key );
db.insertOne('coll1', {value}).then(() => {
callback();
}).catch(err => {callback(err)})
}, function(err) {
// if any of the file processing produced an error, err would equal that error
if( err ) {
// One of the iterations produced an error.
// All processing will now stop.
console.log('err:', err);
reject(err);
} else {
console.log('Col1 All inserts have been processed successfully');
resolve("Success");
}
});
})
}
function func2 () {
return new Promise((resolve, reject) => {
console.log("In Func2");
async.forEachOf(insertList, function(value, key, callback) {
console.log('>>>>' + value + '<<<<<<' + key );
db.insertOne('coll2', {value}).then(() => {
callback();
}).catch(err => {callback(err)})
}, function(err) {
// if any of the file processing produced an error, err would equal that error
if( err ) {
// One of the iterations produced an error.
// All processing will now stop.
console.log('err:', err);
reject(err);
} else {
console.log('Col2 All inserts have been processed successfully');
resolve("Success");
}
});
})
}
function func3 () {
return new Promise((resolve, reject) => {
console.log("In Func3");
async.forEachOf(insertList, function(value, key, callback) {
console.log('>>>>' + value + '<<<<<<' + key );
if(key === 1) {
value = 'a';
}
db.insertOne('coll3', {value}).then(() => {
callback();
}).catch(err => {callback(err)})
}, function(err) {
// if any of the file processing produced an error, err would equal that error
if( err ) {
// One of the iterations produced an error.
// All processing will now stop.
console.log('err:', err);
reject(err);
} else {
console.log('Col3 All inserts have been processed successfully');
resolve("Success");
}
});
})
}
db.connect().then((pool) => {
pool.getConnection((err, connection) =>{
if (err)
return console.error(err);
else {
}
connection.beginTransaction((err) => {
if (err) {
return console.error(err);
}
let func1Promise = new Promise((resolve, reject) => {func1().then(() => {
console.log("Func1 complete");
resolve("Func1 complete");
}).catch((err) => {
console.error("Func1 ERROR: ", err);
reject("Func1 ERROR: ", err);
})});
let func2Promise = new Promise((resolve, reject) => {func2().then(() => {
console.log("Func2 complete");
resolve("Func2 complete");
}).catch((err) => {
console.error("Func2 ERROR: ", err);
reject("Func2 ERROR: ", err);
})});
let func3Promise = new Promise((resolve, reject) => {func3().then(() => {
console.log("Func3 complete");
resolve("Func3 complete");
}).catch((err) => {
console.error("Func3 ERROR: ", err);
reject("Func3 ERROR: ", err);
})});
Promise.all([func1Promise, func2Promise, func3Promise])
.then(()=> {
console.log("All Processes completed successfully.");
connection.commit(err => {
if (err) {
connection.rollback(() => {
throw err;
});
}
console.log('Commit Complete.');
connection.release();
});
})
.catch((err)=> {
console.error(err);
console.error("An update process has failed.");
connection.rollback(() => {
console.error(err);
connection.release();
});
})
});
})
});
The db.js looks like this:
const mysql = require('mysql');
const config = {
db: {
host: 'localhost',
user: process.env.DBUSER,
password: process.env.DBPASSWORD,
database: 'test',
}
};
var pool;
class DB {
constructor(host, user, password, database) {
this.host = host;
this.user = user;
this.password = password;
this.database = database;
}
connect() {
return new Promise((resolve, reject) => {
pool = mysql.createPool({
connectionLimit: 10,
host : this.host,
user : this.user,
password : this.password,
database : this.database
});
resolve(pool);
});
}
objToArray(obj) {
let arr = obj instanceof Array;
return (arr ? obj : Object.keys(obj)).map((i) => {
let val = arr ? i : obj[i];
if(typeof val === 'object' && val !== null)
return this.objToArray(val);
else
return val;
});
}
insertOne(collection, insertObj) {
return new Promise((resolve, reject) => {
pool.getConnection((err, connection) => {
if (err) {
resolve(err);
} else {
let sql = "INSERT INTO " + collection + " VALUES (?)";
// Convert the array of objects into an array of arrays.
let responseJson = this.objToArray(insertObj);
// The query object expects an array of objects so you pass in 'responseJson' as is
console.log(responseJson);
connection.query(sql, [responseJson], (err, result) => {
if (err) {
console.error(err);
return reject(err);
}
//console.log(result);
resolve("SUCCESS: object inserted into database");
});
}
});
});
}
}
const db = new DB(config.db.host, config.db.user, config.db.password, config.db.database);
Object.freeze(db);
module.exports = db;
My database "test" is simple and consists of 3 tables, coll1, coll2, coll3 and each has on field which is type int. In the third function I replace the 1 with 'a' This causes an error and the code catches this error and attempts a rollback, which does not work. If I set a breakpoint after func1 is executed and check the database, the values are already in the database.
Here is the version of MySQL that I am running:
Variable_name,Value
innodb_version,8.0.11
protocol_version,10
slave_type_conversions,
tls_version,"TLSv1,TLSv1.1,TLSv1.2"
version,8.0.11
version_comment,"MySQL Community Server - GPL"
version_compile_machine,x86_64
version_compile_os,macos10.13
version_compile_zlib,1.2.11
I am using the following NPM packages in node:
"async": "^2.6.2",
"mysql": "^2.15.0"
You're creating a transaction on a connection created in your test program, but your db.js's insertOne is grabbing a new connection from the pool that does not have a transaction. You should be passing in the connection you created in the test program.

remaining queries are running event after rollback in a transaction

I have a function to make a transaction as below
utils.sqlTransaction = function (event, callback) {
let connection = mysql.createConnection(dbConfig);
let queryItemPosition = 0;
let queriesData = event;
let resultData = [];
function queryItem() {
if (queryItemPosition > (queriesData.length - 1)) {
connection.commit(function (err) {
if (err) {
connection.rollback(function () {
return callback("Error in processing request commit");
});
}
connection.end();
return callback(null, resultData);
});
} else {
let queryData = queriesData[queryItemPosition] ? queriesData[queryItemPosition].queryData : {};
let parsedQuery = utils.getQuery(queriesData[queryItemPosition].query, queryData);
if (parsedQuery == false) {
connection.rollback(function () {
return callback("\nQuery :-> " + event.query + " <-: not Found!!");
});
}
connection.query(parsedQuery, function (err, result) {
if (err) {
connection.rollback(function () {
return callback(err);
});
}
resultData.push(result);
queryItemPosition++;
queryItem();
})
}
}
connection.beginTransaction(function (err) {
if (err) { return callback(err); }
queryItem();
});
}
I pass data to it as
[{
"query": "some_query",
"queryData": {}
},
{
"query": "someother_query",
"queryData": {}
}]
So that array of queries it handles. But on error even if I call .rollback, it is executing remaining queries. Please help me solve this issue.
NOTE: I am using mysql package
Thanks...
This is because rollback happens as an async function, but your code doesn't wait for it. Change your code to something like:
if (err) {
connection.rollback(function () {
return callback("Error in processing request commit");
});
}
else {
connection.end();
return callback(null, resultData);
}
Similar for all other parts of your code, like:
if (parsedQuery == false) {
connection.rollback(function () {
return callback("\nQuery :-> " + event.query + " <-: not Found!!");
});
}
else {
// continue rest of code here
}
This way the successful flow of your code won't execute in case of errors.

Node.js Wait for redis client.get before send json object

res.json(data) is called before redis client get data from server... How can i wait for data before send json object?
app.get('/api/player/:name', function(req, res) {
var name = req.params.name;
var data = {
"connected": 0,
"health": 0,
"armour": 0
};
readClient.get(name + '.connected', function(err, value) {
data.connected = value;
});
readClient.get(name + '.health', function(err, value) {
data.health = value;
});
readClient.get(name + '.armour', function(err, value) {
data.armour = value;
console.log(data);
});
console.log(data);
res.json(data);
});
Well, redis calls are async. That means that every query call must receive a callback function that shall be called once the query completes injecting data an errors. In order to send res.json when all data is ready then you must do something like:
app.get('/api/player/:name', function(req, res) {
var name = req.params.name;
var data = {
"connected": 0,
"health": 0,
"armour": 0
};
var promises = [];
promises.push( new Promise( function(resolve,reject) {
readClient.get(name + '.connected', function(err, value) {
if(err) { reject(err); }
resolve(value);
});
} ) );
promises.push( new Promise( function(resolve,reject) {
readClient.get(name + '.health', function(err, value) {
if(err) { reject(err); }
resolve(value);
});
} ) );
promises.push( new Promise( function(resolve,reject) {
readClient.get(name + '.armour', function(err, value) {
if(err) { reject(err); }
resolve(value);
});
} ) );
Promise.all(promises).then( function(values) {
console.log(values);
data.connected = values[0];
data.health = values[1];
data.armour = values[2];
res.json(data);
} ).catch(handleError);
});
function handleError(err) {
res.status(501);
res.send({msg:err.message});
}
I'd recommend working with await and Promises however, but this is a good starting point.
Hope this helps

NodeJS Express MySQL post request throwing "Cannot set headers after they are sent to the client"

I try to send some MySQL results back via a express post request, but no matter what I try, there's always the following error:
(node:3743) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at validateHeader (_http_outgoing.js:503:11)
at ServerResponse.setHeader (_http_outgoing.js:510:3)
at ServerResponse.header (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:267:15)
at ServerResponse.send (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:158:21)
at /home/dvs23/projects/Group/Visualizer/index.js:193:11
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:160:7)
(node:3743) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:3743) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
The important part of the code is:
app.post('/getNews', (req, res) => {
var prom = new Promise(function(resolve, reject) {
//create nodes for result
con.connect(function() {
console.log("Connected!");
con.query("SELECT ID,length(Text) FROM news;", function(err, result, fields) {
if (err) {
console.log(err);
reject(err);
return;
}
var nodesArr = [];
result.forEach((row) => {
nodesArr.push({
"id": row["ID"],
"size": row["length(Text)"]
});
});
con.query("SELECT * FROM newsSim;", function(err, result2, fields) {
if (err){
console.log(err);
reject(err);
return;
}
var linksArr = [];
result2.forEach((row) => {
linksArr.push({
"source": row["TID1"],
"target": row["TID2"],
"value": row["SIM"]
});
});
console.log({
"nodes": nodesArr,
"links": linksArr
});
resolve({
"nodes": nodesArr,
"links": linksArr
});
});
});
});
})
.then(function(result) {
console.log(result);
res.send(result); //send result to client
}, function(err) {
console.log("END"+err);
res.send({
"nodes": [],
"links": []
});
});
});
App is my express-app, the request comes from a static HTML page, which is also served by the NodeJS server, via jQuery.
I already use a promise, so how is it possible send is already called??
Also without promises, just nested queries, there's the same error (obviously without the UnhandledPromiseRejectionWarning stuff).
EDIT:
It's not a problem with the nested MySQL-queries, even the following does not work:
app.post('/getNews', (req, res) => {
//var text = req.fields["text"];
var prom = new Promise(function(resolve, reject) {
//create nodes for result
con.connect(function() {
console.log("Connected!");
con.query("SELECT ID,length(Text) FROM news;", function(err, result, fields) {
if (err) {
console.log(err);
reject(err);
return;
}
var nodesArr = [];
result.forEach((row) => {
nodesArr.push({
"id": row["ID"],
"size": row["length(Text)"]
});
});
resolve(nodesArr);
return;
});
});
})
.then(function(news) {
console.log(news);
//if (res.headersSent) return;
res.send(news); //send result to client
}, function(err) {
console.log("END"+err);
//if (res.headersSent) return;
res.send({
"nodes": [],
"links": []
});
});
});
If I use the if(res.headersSent), simply nothing is sent back to my site, neither an empty result, nor the real result, which is fetched as wanted -> I can log it to console...
EDIT2:
app.post('/getNews', (req, res) => {
//var text = req.fields["text"];
req.connection.setTimeout(6000000, function () {
console.log("Timeout 2");
res.status(500).end();
});
var prom = new Promise(function(resolve, reject) {
//create nodes for result
con.connect(function() {
console.log("Connected!");
con.query("SELECT ID,length(Text) FROM news;", function(err, result, fields) {
if (err) {
console.log(err);
reject(err);
return;
}
var nodesArr = [];
result.forEach((row) => {
nodesArr.push({
"id": row["ID"],
"size": row["length(Text)"]
});
});
con.query("SELECT * FROM newsSim;", function(err2, result2, fields2) {
if (err2){
console.log(err2);
reject(err2);
return;
}
var linksArr = [];
result2.forEach((row) => {
linksArr.push({
"source": row["TID1"],
"target": row["TID2"],
"value": row["SIM"]
});
});
resolve({
"nodes": nodesArr,
"links": linksArr
});
return;
});
});
});
})
.then(function(news) {
//if (res.headersSent) return;
res.send(news); //send result to client
console.log(news);
}, function(err) {
console.log("END"+err);
//if (res.headersSent) return;
res.send({
"nodes": [],
"links": []
});
});
});
Tried to set timeout, but Now the error occurs before the timeout callback is called...
(node:9926) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at validateHeader (_http_outgoing.js:503:11)
at ServerResponse.setHeader (_http_outgoing.js:510:3)
at ServerResponse.header (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:267:15)
at ServerResponse.send (/home/dvs23/projects/Group/Visualizer/node_modules/express/lib/response.js:158:21)
at /home/dvs23/projects/Group/Visualizer/index.js:198:11
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:160:7)
(node:9926) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:9926) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Timeout 2
Don't ask me why, but it seems to work now... Possibly setting a specific timeout as suggested by oniramarf was the key. Also added
multipleStatements: true
to mysql.createConnection(...) and moved static stuff to different path:
app.use('/site', express.static('static'));
but that's possibly not the reason it works now :)
Code:
app.post('/getNews', (req, res) => {
//var text = req.fields["text"];
var prom = new Promise(function(resolve, reject) {
//create nodes for result
con.connect(function() {
console.log("Connected!");
con.query("SELECT ID,length(Text) from news;", function(err, result, fields) { //SELECT ID,length(Text) FROM news;
if (err) {
console.log(err);
reject(err);
return;
}
con.query("SELECT * FROM newsSim;", function(err2, result2, fields2) {
if (err2) {
console.log(err2);
reject(err2);
return;
}
var imporIDs = new Set();
var linksArr = [];
result2.forEach((row) => {
if (row["SIM"] > 0.25) {
imporIDs.add(row["TID1"]);
imporIDs.add(row["TID2"]);
linksArr.push({
"source": row["TID1"],
"target": row["TID2"],
"value": row["SIM"]
});
}
});
var nodesArr = [];
result.forEach((row) => {
if (imporIDs.has(row["ID"])){
nodesArr.push({
"id": row["ID"],
"size": row["length(Text)"]
});
}
});
resolve({
"nodes": nodesArr,
"links": linksArr
});
return;
});
});
});
})
.then(function(news) {
//if (res.headersSent) return;
res.send(news); //send result to client
console.log(news);
}, function(err) {
console.log("END" + err);
//if (res.headersSent) return;
res.send({
"nodes": [],
"links": []
});
});
});
var server = http.createServer(app);
server.listen(8080, function() {
console.log('localhost:8080!');
});
server.timeout = 120000;
Edit:
Really seems to be the timeout, after some changes the error occurred again, so I set server.timeout = 12000000 and it worked again :)