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
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
});
}
});
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.
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.
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
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 :)