I'm currently writing an API with a query that takes input as an array and then deletes it in turn via a for statement.
However, if it is not a normal request (ex) result.affectedRows === 0) the DB will generate an UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
How can I solve this?
API CODE
exports.delete = (req, res, next) => {
const arr = req.body.arr
for (let i = 0; i < arr.length; i++) {
let sql = `DELETE FROM post WHERE postId = ?`;
Post.query(sql, [arr[i]], (err, result) => {
if (err) {
return next(err);
}
if (result.affectedRows === 0) {
res.send('Delete failed');
return;
}
});
}
res.send('Successfuly Delete');
};
That error is caused by your code sending multiple http responses, i.e. calling res.send more than once. This happens in your code because return is only breaking out of the callback function passed to Post.query, so the last res.send in the exports.delete function still gets called, causing the error.
You can restructure your code to something like this to avoid sending multiple responses:
function runQuery (sql, data) {
return new Promise((resolve, reject) => {
Post.query(sql, [data], (err, result) => {
if (err) {
reject(err);
} else if (result.affectedRows === 0) {
reject('Delete failed');
} else {
resolve();
}
});
})
}
exports.delete = (req, res, next) => {
const arr = req.body.arr
let sql = `DELETE FROM post WHERE postId = ?`;
let deletes = [];
for (let i = 0; i < arr.length; i++) {
deletes.push(runQuery(sql, arr[i]));
}
Promise.all(deletes).then(() => {
res.send('Successfuly Delete');
}).catch((err) => {
if(err === 'Delete failed') {
res.send(err);
} else {
next(err);
}
})
};
Related
I have this function, that queries my database, and then calls getNumOfSessionForTrainerClientList function:
var select = "select * from USERS WHERE ASSIGNED_TRAINER = ?"
mysqlconn.connect(function(err) {
if (err) {
console.error('Database connection failed: ' + err.stack);
return;
}
mysqlconn.query(select, [req.session.username], async function(err, rows) {
if (err) {
console.log(err);
} else {
let numOfSessionsLeft = {
numberOfSessionsLeftIs: 0
}
for (var i = 0; i < rows.length; i++) {
getNumOfSessionForTrainerClientList(req, res, rows[i], numOfSessionsLeft)
rows[i]["NUM_OF_SESSIONS_PER_MONTH"] = (parseInt(rows[i]["NUM_OF_SESSIONS_PER_MONTH"]) - numOfSessionsLeft.numberOfSessionsLeftIs)
console.log(numOfSessionsLeft)
}
}
mysqlconn.end();
})
})
and then inside the function getNumOfSessionForTrainerClientList, I have this:
async function getNumOfSessionForTrainerClientList(req, res, rows, numOfSessionsLeft) {
var getNumOfSessionForTrainerClientList = "select * from SCHEDULE WHERE CLIENT_USERNAME = ? AND ASSIGNED_TRAINER = ?"
mysqlconn.connect(async function(err) {
if (err) {
console.error('Database connection failed: ' + err.stack);
return;
}
mysqlconn.query(getNumOfSessionForTrainerClientList, [rows["USERNAME"], req.session.username], async function(err, sessionData) {
if (err) {
console.log(err);
} else {
numOfSessionsLeft.numberOfSessionsLeftIs = 1;
console.log(numOfSessionsLeft.numberOfSessionsLeftIs)
}
})
})
}
however, what is happening is that this line : rows[i]["NUM_OF_SESSIONS_PER_MONTH"] = (parseInt(rows[i]["NUM_OF_SESSIONS_PER_MONTH"]) - numOfSessionsLeft.numberOfSessionsLeftIs) is actually assigning 0 to rows[i]["NUM_OF_SESSIONS_PER_MONTH"] because that variable assignment is happening before the function call for getNumOfSessionForTrainerClientList finishes. So it is happening out of sync. I am not sure how to fix this, i have run into issues with async await before, but nothing like this. would appreciate the help.
For loop is synchronous in nature and hence doesn't wait for your method getNumOfSessionForTrainerClientList to return to continue the iteration.
The right way to do is:
Put your code inside an async block and use await() while calling getNumOfSessionForTrainerClientList call.
Second, your function getNumOfSessionForTrainerClientList should
return a promise.
Sample:
async function functionA() {
var select = "select * from USERS WHERE ASSIGNED_TRAINER = ?"
mysqlconn.connect(function(err) {
if (err) {
console.error('Database connection failed: ' + err.stack);
return;
}
mysqlconn.query(select, [req.session.username], async function(err, rows) {
if (err) {
console.log(err);
} else {
let numOfSessionsLeft = {
numberOfSessionsLeftIs: 0
}
for (var i = 0; i < rows.length; i++) {
await getNumOfSessionForTrainerClientList(req, res, rows[i], numOfSessionsLeft)
rows[i]["NUM_OF_SESSIONS_PER_MONTH"] = (parseInt(rows[i]["NUM_OF_SESSIONS_PER_MONTH"]) - numOfSessionsLeft.numberOfSessionsLeftIs)
console.log(numOfSessionsLeft)
}
}
mysqlconn.end();
})
})
}
And your function getNumOfSessionForTrainerClientList should look like:
async function getNumOfSessionForTrainerClientList(req, res, rows, numOfSessionsLeft) {
return new Promise((resolve, reject) => {
var getNumOfSessionForTrainerClientList = "select * from SCHEDULE WHERE CLIENT_USERNAME = ? AND ASSIGNED_TRAINER = ?"
mysqlconn.connect(async function(err) {
if (err) {
console.error('Database connection failed: ' + err.stack);
return reject(err);
}
mysqlconn.query(getNumOfSessionForTrainerClientList, [rows["USERNAME"], req.session.username], async function(err, sessionData) {
if (err) {
console.log(err);
return reject(err);
} else {
numOfSessionsLeft.numberOfSessionsLeftIs = 1;
console.log(numOfSessionsLeft.numberOfSessionsLeftIs)
return resolve();
}
})
})
})
}
And then call functionA() with whatever params you need to.
You call the function that includes async codes therefore getNumOfSessionForTrainerClientList doesn't depend the codes above (starts with(rows[i]["NUM_OF and console) what you should do is
Make the function async by returning prom
execute it with await keyword
async function trainerClientList(){
return new Promise((resolve,reject)=>{
var getNumOfSessionForTrainerClientList = "select * from SCHEDULE WHERE CLIENT_USERNAME = ? AND ASSIGNED_TRAINER = ?"
mysqlconn.connect(async function(err) {
if (err) {
console.error('Database connection failed: ' + err.stack);
resolve()
}
mysqlconn.query(getNumOfSessionForTrainerClientList, [rows["USERNAME"], req.session.username], async function(err, sessionData) {
if (err) {
console.log(err);
} else {
numOfSessionsLeft.numberOfSessionsLeftIs = 1;
console.log(numOfSessionsLeft.numberOfSessionsLeftIs)
}
})
})
resolve()
})
}
for (var i = 0; i < rows.length; i++) {
await trainerClientList()
rows[i]["NUM_OF_SESSIONS_PER_MONTH"] = (parseInt(rows[i]["NUM_OF_SESSIONS_PER_MONTH"]) - numOfSessionsLeft.numberOfSessionsLeftIs)
console.log(numOfSessionsLeft)
}
I am trying to run a query to get some data from a table, then use that array of data to get some data from another table to then return it as JSON.
I have been trying for a while but I cannot seem to figure out async and await. Right now it does sort of work but doesn't wait for my second query in the for loop to finish before returning data.
app.get("/get-top-trending", (request, response) => {
const req = request.query
let query = 'SELECT Ticker, Mentions FROM trend_data ORDER BY Date DESC, ' + req.by + ' DESC LIMIT 3';
let returnData = {};
cryptoDB.query(query, (err, tickers) => {
if (err) throw err;
getData(tickers).then(function() {
response.send(returnData)
});
});
async function getData(tickers) {
for (let i = 0; i < tickers.length; i++) {
cryptoDB.query('SELECT HistoricalJSON FROM historical_trend_data WHERE Ticker=? LIMIT 1', [tickers[i]['Ticker']], (err, rows2) => {
if (err) throw err;
returnData[tickers[i]['Ticker']] = rows2[0]['HistoricalJSON'];
});
}
}
});
I assume that something has to be done in the getData async function, however I am not particularly sure how to implement a working solution. I have tried promises but they don't seem to work the way that I expect.
Any guidance would be appreciated.
first solution:
app.get("/get-top-trending", (request, response) => {
const req = request.query
let query = 'SELECT Ticker, Mentions FROM trend_data ORDER BY Date DESC, ' + req.by + ' DESC LIMIT 3';
cryptoDB.query(query, (err, tickers) => {
if (err) throw err;
getData(tickers).then(function (returnData) {
response.send(returnData)
});
});
async function getData(tickers) {
const returnData = {};
const querys = ((ticker) => {
return new Promise((resolve, reject) => {
cryptoDB.query('SELECT HistoricalJSON FROM historical_trend_data WHERE Ticker=? LIMIT 1', [ticker['Ticker']], (err, rows2) => {
if (err) reject(err);
returnData[ticker['Ticker']] = rows2[0]['HistoricalJSON'];
resolve();
});
})
})
for (let i = 0; i < tickers.length; i++) {
await querys(tickers[i]);
}
return returnData
}
});
second solution:
app.get("/get-top-trending", (request, response) => {
const req = request.query
let query = 'SELECT Ticker, Mentions FROM trend_data ORDER BY Date DESC, ' + req.by + ' DESC LIMIT 3';
cryptoDB.query(query, (err, tickers) => {
if (err) throw err;
getData(tickers).then(function(returnData) {
response.send(returnData)
}).catch(error => throw error);
});
async function getData(tickers) {
let returnData = {};
for (let i = 0; i < tickers.length; i++) {
returnData[tickers[i]['Ticker']] = await getTickerQuery([tickers[i]['Ticker']]);
}
return returnData;
}
function getTickerQuery(ticker) {
return new Promise((resolve, reject) => {
cryptoDB.query('SELECT HistoricalJSON FROM historical_trend_data WHERE Ticker=? LIMIT 1', ticker, (err, rows2) => {
if (err) throw reject(err);
resolve(rows2[0]['HistoricalJSON']);
});
})
}
});
I recommend second solution for readability
I'm studying to create a simple API with mysql. I've understood and implemented the simple structure in which the app call the router, that call the controller, that call the service. But now i'm developing a multiple tag service module and I've realized that I need to call the same sql queries services declared in it. I show you the code for a better understanding:
tag_service.js:
const mysql = require("../../config/database");
module.exports = {
insertTags: async (data, callBack) => {
const connection = await mysql.connection();
let results = '';
const tagsArray = data.tags.map(tag => [data.id_manager,data.cod_table,data.id_record,tag])
try {
//console.log("at insertCallout...");
await connection.query("START TRANSACTION");
results = await connection.query(
`INSERT INTO s_com_tags (id_manager,cod_table,id_record,tag)
VALUES (?,?,?)`,
[tagsArray]
);
await connection.query("COMMIT");
} catch (err) {
await connection.query("ROLLBACK");
//console.log('ROLLBACK at insertCallout', err);
throw err;
} finally {
await connection.release();
return callBack(null, results);
}
},
deleteTags: async (data, callBack) => {
//console.log(data);
let results = '';
const connection = await mysql.connection();
try {
//console.log("at deleteCallouts...");
await connection.query("START TRANSACTION");
results = await connection.query(
`DELETE FROM s_com_tags
WHERE cod_table = ? AND id_record = ? AND tag IN (?)`,
[data.code_table, data.id_record,data.tags]
);
//console.log(res);
await connection.query("COMMIT");
} catch (err) {
await connection.query("ROLLBACK");
//console.log('ROLLBACK at deleteCallouts', err);
throw err;
} finally {
await connection.release();
return callBack(null, Callouts);
}
},
};
controller's structure that will use the service:
module.exports = {
updateLabDesc: async (req, res, next) => {
try {
const body = req.body;
if(!body.internal_code){
updateLabDesc(body.manager, async (err, results) => {
if (err) {
return next(createError.InternalServerError())
}
});
}
updateTags(body, async (err, results) => {
if (err) {
return next(createError.InternalServerError())
}
return res.json({
success: (results ? 1 : 0 ),
message: (results || 0) + " LabDesc inserted successfully"
});
});
} catch (error) {
next(error)
}
},
};
But the update is something like
updateTag function => {
try {
const current_tags = await getTags(req.body);
let newTags = [];
let oldTags = [];
req.body.tags.forEach(tag => {
if(!current_tags.includes(tag))
newTags.push(tag)
});
await insertTags(newTags);
current_tags.tags.forEach(tag => {
if(!req.body.tags.includes(tag))
oldTags.push(tag)
});
await deleteTags(oldTags);
} catch (error) {
next(error)
}
},
Basically, the tag_service has insertTags and deleteTags but I need the updateTags to call these functions as well. The final controller will call insertTags, deleteTags and updateTags. How can I structure these calls?
It is a controller that could call 2 helpers (insertTag and deleteTags) and another helper (updateTags) that call these 2 helpers. Any ideas?
My User model has a field of type Array with values like this
"courseId" : [
"5ac1fe64cfdda22c9c27f264",
"5ac207d5794f2910a04cc9fa",
"5ac207d5794f2910a04cc9fa"
]
My routes are configured in this way:
router.get('/:userid/vendor-dashboard', function (req, res) {
var courseID = [];
User.findById(req.params.userid, function (err, doc) {
if (err) {
res.send(err);
} else {
for (var i = 0; i < doc.courseId.length; i++) {
Course.findById(doc.courseId[i], function (err, course) {
if (err) {
console.log(err);
} else {
console.log(course.title);
courseID.push(course.title);
}
})
}
res.send(JSON.stringify(courseID));
}
})
})
First I'm finding a user and when user is found, He should find all the courses in the array and display their title.
Now, I'm able to get the title in console, but when I try to send it through res.send, it shows an empty array.
What am I doing wrong?
The main problem is that you are sending the response before getting the response from Course model.
The correct way using callback will be:
router.get('/:userid/vendor-dashboard', function(req, res) {
var courseID = [];
User.findById(req.params.userid, function(err, doc) {
if (err) {
return res.send(err);
}
var resolved = 0;
for (var i = 0; i < doc.courseId.length; i++) {
Course.findById(doc.courseId[i], function(err, course) {
if (err) {
return console.log(err);
}
courseID.push(course.title);
if (++resolved === doc.courseId.length) {
res.send(JSON.stringify(courseID));
}
})
}
})
})
I need some advice on how to re/write the db specific cascading code (callback) so that I can effectively return a value to the underlying if/else.
I am using restify and the db lib is node-mssql (tedious).
function authenticate(req, res, next) {
var auth = req.authorization;
var err;
if (auth.scheme !== 'Basic' || ! auth.basic.username || ! auth.basic.password) {
authFail(res);
err = false;
} else {
var sql = "SELECT ..."
var connection = new mssql.Connection(config.mssql, function(err) {
if (err) {console.log(err);}
var request = connection.request();
request.query(sql, function(err, recordset) {
if (err) {console.log(err);}
if (recordset.length === 0) {
authFail(res);
err = false; // <--- I need to be able to return this
} else {
authSuccess();
}
});
});
}
next(err);
}
I've reviewed the suggested duplicate, and while I think, I understand the issue, I can't work out the cleanest (lets be honest any) way to get this to work.
How about using Promises?
function authenticate(req, res, next) {
var auth = req.authorization;
if (auth.scheme !== 'Basic' || ! auth.basic.username || ! auth.basic.password) {
authFail(res);
next(false);
} else {
var sql = "SELECT ..."
var connection = new mssql.Connection(config.mssql, function(err) {
if (err) {console.log(err);}
var request = connection.request();
request.query(sql).then(function(recordset) {
if (recordset.length === 0) {
authFail(res);
return false; // <--- return this
} else {
authSuccess();
}
}).catch(function(err) {
console.log(err);
return err;
}).then(function(err) { next(err); });
});
}
}