NodeJS application not waiting for response from MySQL database - javascript

I'm trying to setup a NodeJS application with GraphiQL API and MySQL database connection. All of it seem to work until I'm trying to get the data that was fetched from the database be available for GraphQL to be able to do something with it.
Here we have the app.js file, which is the starting point of the backend. Assume that all the imports and declarations are valid.
app.use('/api', graphql({
schema: buildSchema(schema),
rootValue: resolvers,
graphiql: true
}));
The rootValue is as follows.
const resolvers = {
regions: () => {
var a = manager.getRegions();
console.log("a: " + a);
return a;
}
};
The manager object. I have the manager object incase I want to change database type in the future.
const manager = {
getRegions : function() {
console.log("getRegions entered...");
return processQuery("regions");
}
};
Finally, under the MySQL script we have.
const processQuery = (query) => {
var res = null;
switch (query) {
case 'regions':
default:
db.query(SELECT_REGIONS_QUERY, (err, rows) => {
if (err) {
throw err;
} else {
res = JSON.stringify(rows);
console.log("Stringify: " + res);
}
});
}
return res;
}
I've read numerous pages and even stackoverflow posts about Promise, callback functions and async/await but neither (atleast to code attempts made by me) seem to make the printout under the rootValue be printed last...
I saw an implementation done by Academind that uses MongoDB instead and he doesn't seem to have to care about this issue. Any ideas on how to solve this? Thank you!

What you can to is make the processQuery an asynchronous function and just wait for the db.query to be solved.
const processQuery = async (query) => {
var res = null;
switch (query) {
case 'regions':
default:
await db.query(SELECT_REGIONS_QUERY, (err, rows) => {
if (err) {
throw err;
} else {
res = JSON.stringify(rows);
console.log("Stringify: " + res);
}
});
}
return res;
}

Related

Modifying content in file

I run into a problem, which I cant solve.
Im making an app, where on the first page I need to choose one of two machines, there are 2 buttons on page and when one of them is clicked, i make POST to /mechineChoose where I pass id of selected machine. Then I need to change config.js file, where I have all params needed for rest of app.
const config = {
machineName: "Machine",
...
So in my code I need to change machineName, right now I use fs module to read and then write to file, but problem is that I cant change this name more than once. When I restart app, Im able to change the name, but when trying to choose second machine, nothing happens.
router.post("/machineChoose", async (req, res) => {
console.log(req.body.machineChoose);
if (req.body.machineChoose == 1) {
machineX = "Machine1";
} else {
machineX = "Machine2";
}
console.log(machineX);
fs.readFile('./config.js', 'utf-8', function (err,data){
if (err){
console.log(err);
}
var result = data.replace(config.machineName,machineX);
fs.writeFileSync('./config.js', result, 'utf-8', function(err){
if (err) return console.log(err);
});
});
return res.send("")
})
Any idea how to solve it ?
After writing to the file, you need to reload the config-object as it will still hold the previous state in-memory and thus further calls to data.replace(...) will not replace anything, since it will still be called with "Machine".
I would do something like this (although you should consider using a real database):
router.post("/machineChoose", async (req, res) => {
const chosenMachine = req.body.machineChoose == 1 ? "Machine1" : "Machine2";
const config = await readConfig();
config.machineName = chosenMachine;
await writeConfig(config);
res.status(204).end();
});
async function writeConfig(currentConfig) {
try {
await fs.promises.writeFile("./config.json", JSON.stringify(currentConfig));
} catch (e) {
console.log("Could not write config file", e)
throw e;
}
}
async function readConfig() {
try {
const rawConfig = await fs.promises.readFile("./config.json", {encoding: 'utf-8'});
return JSON.parse(rawConfig);
} catch (e) {
console.log("Could not read config file", e)
throw e;
}
}

Getting Query object instead of results with Async/await function and npm-mysql

I have this function which is async and i'm trying to make a simple query from npm-mysql db.
let sortCategory = async (id) => {
try {
var sql = 'SELECT * FROM categories WHERE parent_id=?';
var results = await connection.query(sql, id);
// console.log(results);
return(results);
} catch(err) {
console.log(err);
return false;
}
}
But instead of results inside the results variable i just get the query object.
Query {
_events:
[Object: null prototype] {
error: [Function],
packet: [Function],
timeout: [Function],
end: [Function] },
_eventsCount: 4,
_maxListeners: undefined,
_callback: undefined,
_callSite:
Error
at Protocol._enqueue (C:\Users\fedesc\Sites\borsalino\node_modules\mysql\lib\protocol\Protocol.js:144:48)
at Connection.query (C:\Users\fedesc\Sites\borsalino\node_modules\mysql\lib\Connection.js:198:25)
at sortCategory (C:\Users\fedesc\Sites\borsalino\server\routes\categories.js:35:38)
at router.post (C:\Users\fedesc\Sites\borsalino\server\routes\categories.js:48:31)
at process._tickCallback (internal/process/next_tick.js:68:7),
_ended: false,
_timeout: undefined,
_timer: Timer { _object: [Circular], _timeout: null },
sql: 'SELECT * FROM categories WHERE parent_id=\'0\'',
values: '0',
.... }
The query as seen in object is
sql: 'SELECT * FROM categories WHERE parent_id=\'0\'',
values: '0',
EDIT#1
an async/await for INSERT query does works. it's only when i need to retrieve data back that i don't get it.
but i can't manage to get the results back even though i do have some in table that should return.
i feel like there is something i still not quite understand about mysql and async calls.
thanks guys.
I use async/await of mysql query like this:
var getCategories = function (id) {
return new Promise(function (resolve, reject) {
var sql = `SELECT * FROM categories WHERE parent_id=?`;
connection.query(sql, [id], function (err, result) {
if (!err) {
resolve(result);
} else {
resolve({
status: "error",
message: "Error Getting Data",
debug: err
});
}
});
});
};
try {
var categories = await getCategories();
} catch (error) {
console.log(error);
}
Above code is very different from yours but you can use the above method to use in further case
Thank you for your helpful posts fedesc. I’d been struggling with this for days. Based on your lead, I ended up with this which is elegant relative to my earlier attempts:
'use strict';
const mysql = require('mysql');
const config = require('./config.js');
const util = require('util'); // needed for async support
const ac = awaitableConnection( config );
demoAwait();
async function demoAwait() {
try {
const results1 = await ac.query( 'SELECT * FROM table' );
const results2 = await ac.query( 'SELECT * FROM table WHERE whatever' );
console.log(results1); // all above results are available
// throw 'test error'; uncomment to test an error
} catch ( err ) {
console.log(err);
} finally {
await ac.close();
}
}
function awaitableConnection( config ) { // wrapped in a promise
const connection = mysql.createConnection( config );
return {
query( sql, args ) {
return util.promisify( connection.query )
.call( connection, sql, args );
},
close() {
return util.promisify( connection.end ).call( connection );
}
};
}
The technique remains readable when queries are placed in a loop. I have to acknowledge Michał Męciński for the pattern of this technique. In Sept 2019 he updated the article fedesc linked above while taking advantage of node.js 8 or later. The article also demonstrates how to use a similar technique for transactions. Node.js, MySQL and async/await
As I can see in documentation https://www.npmjs.com/package/mysql
connection.query('SELECT * FROM `books` WHERE `author` = ?', ['David'], function (error, results, fields) {
// error will be an Error if one occurred during the query
// results will contain the results of the query
// fields will contain information about the returned results fields (if any)
});
You code should became
var sql = 'SELECT * FROM categories WHERE parent_id=?';
connection.query(sql, [id], function (error, results, fields) {
if(error){
return error;
}
return results;
});
1st of all thank you kind responders.
The answer of both of you was indeed the same and the correct one. So i just accepted the quickest responder.
NPM Mysql functions do operate in an old school callback style (and needs to be updated) What was really strange for me is that an INSERT statement did work out of the box - I guess this is because you don't really need a callback if you don't need data to be retrieved.
And async/await is part of node and not mysql.
So the call did indeed fired but without a callback.
Connection.prototype.query = function query(sql, values, cb) {
var query = Connection.createQuery(sql, values, cb);
query._connection = this;
if (!(typeof sql === 'object' && 'typeCast' in sql)) {
query.typeCast = this.config.typeCast;
}
if (query.sql) {
query.sql = this.format(query.sql, query.values);
}
if (query._callback) {
query._callback = wrapCallbackInDomain(this, query._callback);
}
this._implyConnect();
return this._protocol._enqueue(query);
};
Therefore your answers are accurate and correct.
Allow me to elaborate on a possible solution i found for my problem with the hope that maybe it'll help readers who face this approach issue as well.
There is a workaround i've found when still searching for a solution here - How to use classes in Node.js (with no pre-compilers), and why you should and here - Node.js, MySQL and promises
The solution was "promisifying" mysql functions with a class alike function that converts all mysql functions to promises.
Which than will give the option to work with database in an async/await approach.
Also there are tools that promisify functions that uses callbacks like this one here
//mysql
const mysql = require('mysql');
function Database() {
this.connection = mysql.createConnection({
host : 'localhost',
user : '*************',
password : '*************',
database : '*************',
multipleStatements: true
});
this.query = (sql, args) => {
return new Promise((resolve, reject) => {
this.connection.query(sql, args, (err, rows) => {
if (err)
return reject(err);
resolve(rows);
});
});
};
this.close = () => {
return async () => {
try {
this.connection.end(err => {
if (err) throw err;
return;
});
} catch(e) {
return e;
}
}
};
};
var connection = new Database();
Setting you db connection this way will allow you now to use async/await as in original question.
let sortCategory = async (id) => {
try {
var sql = 'SELECT * FROM categories WHERE parent_id=?';
var results = await connection.query(sql, id);
// console.log(results);
return results;
} catch(err) {
console.log(err);
return false;
}
}
Hope this helps anyone.

How can I use a variable saved from a mysql connection with NodeJS to an asynchronous function?

I'm trying to scrape a website with Puppeteer. I want to select the date of the last post inserted in my database and compare it to the dates taken by the scrape so I can see if the post is already in the database (using the date as the reference to see if it has been modified).
Here is my code:
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'db_webcrawler_coches'
});
connection.connect((err) => {
if (err) throw err;
console.log('Connected!');
});
let lastpublishedDate;
let idCoches;
connection.query("SELECT id_coches, publish_date FROM coches ORDER BY publish_date DESC limit 1", function (err, row) {
if (err) throw err;
lastPublishedDate = row[0].publish_date;
idCoches = row[0].id_cochesNet;
console.log("Published in", lastPublishedDate);
console.log("Id Coches", idCoches);
});
const run = async () => {
try {
const options = {
headless: false,
};
...
const news = await page.evaluate(() => {
const idsList = [...document.querySelectorAll('div.mt-SerpList-item')].map(elem => elem.getAttribute("id")).filter(elem => elem.includes("#"))
const datePost = [...document.querySelectorAll('span.mt-CardAd-date')].map(elem => elem.innerText);
for(let i = 0; i < titlesCar.length; i++){
const finalDate = parsedDates[i];
if (finalDate > lastPublishedDate || idCoches !== idsList[i]){
console.log("Not repeated");
const carsList[i] = [
idsList[i],
parsedDates[i]
]
} else {
console.log("Repeated")
}
}
return carsList;
});
...
} catch (err) {
console.log(err);
await browser.close();
console.log("Browser Closed");
}
};
run();
As you can see I want to see if the date is the same or not as well as the id taken from the query. However, it appears an error that says Evaluation failed: ReferenceError: variable "lastPublishedDate" is not defined and I imagine that it will be the same with "idCoches". I wrote some console.logs to see when it crashes and it seems that it happens when reaches the function "news".
I'm not sure if it is because it is the scope or because of the function. What do you think I should do to make it work?
Could it be the scope?
Thank you!
EDIT: SOLVED!
I post it in the case that anyone faces a similar issue.
Indeed it was the scope, it is a problem related to Puppeteer. It seems that the function with page.evaluate() is unable to take any variable outside of it. To change it you need to add the page.evaluate in the following way:
await page.evaluate((variable_1, variable_2) => { /* ... */ }, variable_1, variable_2);
The callback to your Query probably does has not returned yet when the async function is run, so whatever your trying to reference is not defined.
I'm not sure if your mysql client supports promises, but if it does you could do something like this:
const run = async () => {
const row = await connection.query("SELECT id_coches, publish_date FROM coches ORDER BY publish_date DESC limit 1")
lastPublishedDate = row[0].publish_date;
idCoches = row[0].id_cochesNet;
...
}
If that does not work you could also run everything inside the callback of the query. Hope that helps.

Why is my asynch mongodb query function hanging?

First of all, please forgive me if this is a duplicate, I am new to coding and Javascript in general.
I have an async function that queries mongodb based on an objects passed in the function call. The function executes, and returns the results to a callback function which logs the results to the console, and then hangs. Ultimately, I want to take the results of the async query and then do something with them outside the original async function. I am not understanding why it hangs after it logs to the console.
const MongoClient = require('mongodb').MongoClient;
let fObj = {
field : {},
limit : 100
}
let cObj = {
dbName : 'myNewDatabase',
colName : 'newCollection'
}
async function findDoc(cObj,fObj) {
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url, { useNewUrlParser: true });
try {
await client.connect();
const db = client.db(cObj.dbName);
const col = db.collection(cObj.colName);
console.log(`Connection Made to ${db.databaseName} database.`);
return await col.find(fObj.field).limit(fObj.limit).toArray();
client.close();
} catch (err) {
console.log(err.stack);
}
};
findDoc(cObj,fObj).then(function(result) {
console.log(result);
});
The code executes, and logs the results to the console, but then hangs. I have to ctrl-c out to get it to end. What am I missing?
I suppouse you're running your code with NodeJs. This implies that you have a promise hanging up, which keeps the server running. I assume this is because your connection to the DB is still open after you have found the document.
You need to move your client.close(); statement above the return statement, because it is never reached otherwise and your server will hang up forever.
Your code will look like this in the end:
const MongoClient = require('mongodb').MongoClient;
let fObj = {
field : {},
limit : 100
}
let cObj = {
dbName : 'myNewDatabase',
colName : 'newCollection'
}
async function findDoc(cObj,fObj) {
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url, { useNewUrlParser: true });
try {
await client.connect();
const db = client.db(cObj.dbName);
const col = db.collection(cObj.colName);
console.log(`Connection Made to ${db.databaseName} database.`);
const result = await col.find(fObj.field).limit(fObj.limit).toArray();
client.close();
return result;
} catch (err) {
console.log(err.stack);
}
};
findDoc(cObj,fObj).then(function(result) {
console.log(result);
});
Also, I advise you to enclose your whole async function's body into the try clause. This way you will be able to effectively intercept any error. Imagine your new MongoClient failed to instantiate - you would end up with an uncaught error inside a promise, which isn't very nice.

NodeJs facing the issue while using async waterfall

I am calling 2 API request 1 after another so I decided to use the waterfall model but I am facing the issue in it
I have tried so much but not able to solve the issue.
Below is my code:
var unirest = require("unirest");
var async = require("async")
exports.user = (req, res, next) => {
const qry = req.params.id
async.waterfall([
(nextCall) => {
var req = unirest("GET", API_URL1);
req.query({
// some query
});
req.headers({
// some headers
});
req.end(function(subCount) {
// if (resp.error) throw new Error(resp.error);
var channelSubCount = subCount.body
nextCall(null, data)
});
},
(data, nextCall => {
console.log(channelSubCount, 'data')
var reqs = unirest("GET", API_URL2);
reqs.query({
// some query
});
reqs.headers({
// some headers
});
reqs.end(function(res) {
// if (res.error) throw new Error(res.error);
console.log(res.body);
return nextCall(null, {
name: 'abc',
photo: 'src',
count: data
})
});
})
], function(finalData) {
// if (error) { alert('Something is wrong!'); }
console.log('final')
res.status(200).json(
finalData
);
});
};
ERROR:
Reference Error: data is not defined
I don't understand why this is happening.
Also some please show me the right way to implement the above things with optimizations.
Any help appreciated...
Looks like you forgot to close parentheses here in your second arrow function definition:
(data, nextCall => {
It's still a valid JavaScript, but the interpreter now treats data not as a function incoming parameter (as you need), but as a variable. But it's not defined anywhere, therefore you have that error.
Make it like this and it will work:
(data, nextCall) => {

Categories