I'm using NodeJS and mysql2 to store data in my database. but their are times when I need to perform database saves synchronously, like this example:
if(rent.client.id === 0){
//Save client
connection.query('INSERT INTO clients (name, identity, licence, birthDate, address, phoneNumber, email) VALUES (?,?,?,?,?,?,?)',
[/*Array of values*/],
function (err, results) {
if (err) throw err;
//Retrieve client id to use it in the next database save
rent.client.id = results.insertId;
})
}
//Save rent
connection.query('INSERT INTO rents (deliveryDate, returnDate, gasLevel, deliveryPoint, deliveryPrice, unitPrice, state, clientID, carID) VALUES (?,?,?,?,?,?,?,?,?)',
[/*Array of values that contain the last inserted id clients*/],
function (err, results) {
if (err) throw err;
console.log('rent saved', results);
})
So how can I perform these two database saves synchronously. I don't think that doing it in the following manner is good code:
connection.query(queryString,
[queryValues],
function (err, results) {
if (err) throw err;
connection.query(queryString,
[queryValues],
function (err, results) {
if (err) throw err;
console.log('rent saved', results);
})
})
So what kind of solutions do you propose?
I don't think that doing it in the following manner is good code
It isn't, but only because of the
if (err) throw err;
part, which will not do anything useful. (It certainly won't make your function making these query calls throw an exception; it can't, you function has already returned. All it does is throw an exception from the callback; query probably ignores it.)
Other than that, it's the correct way to do this with NodeJS-style callbacks. More specifically:
function myFunctionToDoThisWork(callback) {
connection.query(queryString1,
[queryValues1],
function (err, results) {
if (err) {
callback(err);
return;
}
connection.query(queryString2,
[queryValues2],
function (err, results) {
if (err) {
callback(err);
return;
}
console.log('rent saved', results);
});
});
}
There are couple of things you can do to make that code easier to maintain:
One is to use promises, which you can use on any vaguely-recent version of Node (or via an npm module). First we'd give ourselves a Promise-enabled version of query. In Node v8 and above, you can do that like this:
const promisify = require("utils").promisify;
// ...
const queryPromise = promisify(connection.query.bind(connection));
Alternately there's the promisify npm module, or really this basic version is really trivial:
function promisify(f) {
return function() {
var t = this;
return new Promise(function(resolve, reject) {
var args = Array.prototype.slice.call(arguments);
args.push(function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
f.apply(t, args);
});
};
}
Then:
function myFunctionToDoThisWork() {
return queryPromise(queryString1, [queryValues1])
.then(() => {
return queryPromise(queryString2, [queryValues2]);
})
.then(() => {
console.log('rent saved', results);
});
});
}
then consume it via:
myFunctionToDoThisWork().then(/*...*/).catch(/*...*/);
On Node v8 and higher, you can take that further with async and await:
async function myFunctionToDoThisWork() {
await queryPromise(queryString1, [queryValues1]);
await queryPromise(queryString2, [queryValues2]);
console.log('rent saved', results);
}
If you call it from an async function, you'd consume it via await. If calling it from a non-async function, you consume it just like the promise version above (via then).
Related
I've already searched among a lot of questions asked by other users but I can't still figure out why my function doesn't work properly.
I wrote this code:
async function getFile(c, sourcePath, fileName, destPath) {
return await new Promise((resolve, reject) => {
c.cwd(sourcePath, function(err, currentDir) { // "c" is an instance of ftp module's class, "cwd" is one of its methods to change working directory
err = new Error('trying to throw an error for testing the func');
if (err) reject(err);
c.get(fileName, function(err, stream) {
if (err) reject(err);
stream.once('close', function() { resolve(); });
stream.pipe(fs.createWriteStream(destPath));
});
});
}));
}
(async function() {
try {
await getFile(c, '/path/to', 'file.xls', 'file.xls');
} catch (err) {
throw err;
}
})();
Node returns an UnhandledPromiseRejectionWarning, and I don't understand why. I have returned a promise in the first function and then called that function in another one correctly structured with async-await syntax. What is wrong with my code?
I am just starting in ExpressJS. So here console.log works, so if I make a call, I do see it in terminal result dataset printed. But, it doesn't return it as response. I do console.log and return but still get no data in response..
index.js
...
app.get("/", async (req, res) => {
let data = await queries();
res.json(data);
});
...
Also tried
...
app.get("/", (req, res) => {
res.json(queries());
});
...
queries.js
function main(sql) {
return pool.getConnection((err, connection) => {
if (err) throw err;
return connection.query(sql, (errt, rows) => {
connection.release(); // return the connection to pool
if (errt) throw err;
console.log(rows);
return rows;
});
});
}
function queries() {
let Q = "SELECT * FROM `items`";
return main(Q);
}
I believe the problem is in the function main() if you do see the result in the terminal. Your functions in the index.js file seems okay. The issue is probably due to the promise not being sent back from the main() function in queries.js file. You need to do 2 things -
Wrap the functionality of the main() in a Promise and return that promise. The callback function of the Promise object takes resolve(use this to return result) and reject(use this to return error).
As the main() will return a promise, you queries() function needs to be async, therefore needs to await the return from the main().
I have attached an Image to show how I implemented this for a DNS-lookup function. You will find some similarities.
// Notice the resolve and reject are functions
function main(sql) {
return new Promise((resolve, reject) => {
pool.getConnection((err, connection) => {
if (err) throw err;
connection.query(sql, (errt, rows) => {
connection.release(); // return the connection to pool
if (errt) throw err;
console.log(rows);
resolve(rows);
});
});
});
}
async function queries() {
let Q = "SELECT * FROM `items`";
let result = await main(Q);
return result;
}
I believe this should work. Give it a try and good luck!
I have the following function that gets a hexcode from the database
function getColour(username, roomCount)
{
connection.query('SELECT hexcode FROM colours WHERE precedence = ?', [roomCount], function(err, result)
{
if (err) throw err;
return result[0].hexcode;
});
}
My problem is that I am returning the result in the callback function but the getColour function doesn't return anything. I want the getColour function to return the value of result[0].hexcode.
At the moment when I called getColour it doesn't return anything
I've tried doing something like
function getColour(username, roomCount)
{
var colour = '';
connection.query('SELECT hexcode FROM colours WHERE precedence = ?', [roomCount], function(err, result)
{
if (err) throw err;
colour = result[0].hexcode;
});
return colour;
}
but of course the SELECT query has finished by the time return the value in colour
You have to do the processing on the results from the db query on a callback only. Just like.
function getColour(username, roomCount, callback)
{
connection.query('SELECT hexcode FROM colours WHERE precedence = ?', [roomCount], function(err, result)
{
if (err)
callback(err,null);
else
callback(null,result[0].hexcode);
});
}
//call Fn for db query with callback
getColour("yourname",4, function(err,data){
if (err) {
// error handling code goes here
console.log("ERROR : ",err);
} else {
// code to execute on data retrieval
console.log("result from db is : ",data);
}
});
If you want to use promises to avoid the so-called "callback hell" there are various approaches.
Here's an example using native promises and the standard MySQL package.
const mysql = require("mysql");
//left here for testing purposes, although there is only one colour in DB
const connection = mysql.createConnection({
host: "remotemysql.com",
user: "aKlLAqAfXH",
password: "PZKuFVGRQD",
database: "aKlLAqAfXH"
});
(async () => {
connection.connect();
const result = await getColour("username", 2);
console.log(result);
connection.end();
})();
function getColour(username, roomCount) {
return new Promise((resolve, reject) => {
connection.query(
"SELECT hexcode FROM colours WHERE precedence = ?",
[roomCount],
(err, result) => {
return err ? reject(err) : resolve(result[0].hexcode);
}
);
});
}
In async functions, you are able to use the await expression which will pause the function execution until a Promise is resolved or rejected. This way the getColour function will return a promise with the MySQL query which will pause the main function execution until the result is returned or a query error is thrown.
A similar but maybe more flexible approach might be using a promise wrapper package of the MySQL library or even a promise-based ORM.
I'm writing a route that takes in user-input data from a form and uses that data to execute a few separate queries on my database. That is all well-tested, and I know I can get the user input, query the database, and return the results. The part giving me trouble, however, is that I want to randomly select a few results out of all of the returned data, so I am pushing the results into one big array. But since the queries execute asynchronously, the only way to access the data is in the callback and of course if I tried to push the rows in the callback, I still can't access that array outside of the callback functions because that code will execute before the queries are even run. So I'm attempting to use JavaScript async/await syntax to do the queries, as follows:
module.exports = {
search: (req, res) => {
function saveResults(resultsObj) {
Object.keys(resultsObj).forEach(function (key) {
var row = resultsObj[key];
var song = row.song_name
resultsArray.push(song);
return resultsArray;
});
}
async function queryForSong(){
var songGenre;
getSongGenre = "The queries aren't important, I know they work as expected";
await mysqlConnection.query(getSongGenre, (err, rows) => {
if (err) throw err;
songGenre = row[0].song_genre;
songSearchQuery = "SELECT query";
mysqlConnection.query(songSearchQuery, (err, rows) => {
if (err) throw err;
return saveResults(rows);
});
});
}
async function queryForArtist() {
var artistID;
getArtistID = "SELECT query";
await mysqlConnection.query(getArtistID, (err, rows) => {
if (err) throw err;
artistID = rows[0].artist_id;
console.log(rows, artistID);
artistSearchQuery = "SELECT query";
mysqlConnection.query(artistSearchQuery, (err, rows) => {
if (err) throw err;
return saveResults(rows);
});
});
}
async function queryForGenre() {
genreSearchQuery = "SELECT query";
await mysqlConnection.query(genreSearchQuery, (err, rows) => {
if (err) throw err;
return saveResults(rows);
});
}
async function queryForDecade() {
decadeSearchQuery = "SELECT query";
await mysqlConnection.query(decadeSearchQuery, (err, rows) => {
if (err) throw err;
return saveResults(rows);
});
}
var resultsArray = [];
var artist = req.body.searchArtist;
var song = req.body.searchSong;
var genre = req.body.searchGenre;
var decade = req.body.searchDecade;
if (song) {
queryForSong().then((value) => console.log(value)); //these all log undefined
}
if (artist) {
queryForArtist().then((value) => console.log(value));
}
if (genre) {
queryForGenre().then((value) => console.log(value));
}
if (decade) {
queryForDecade().then((value) => console.log(value));
}
}
}
Then the idea is to save the resolved rows from the promises into one big array and randomize it from there. My only thought of why this isn't working is that it could be because the return statements are located inside the callback functions? I have also tried it using promises directly as this post explains, by writing all of my async functions like this instead:
function queryForGenre() {
genreSearchQuery = "SELECT query";
return new Promise(function (resolve, reject) {
mysqlConnection.query(genreSearchQuery, (err, rows) => {
if (err) reject(err);
resolve(saveResults(rows));
});
});
}
But either way, the console.log statements in the .then still print undefined, meaning my promises aren't resolving to the saveResults(rows) value that I give it. Do I need to make the saveResults function asynchronous as well? From my understanding of promises, this seems like it should work. What am I missing? Or is there a simpler way to do this?
Your functions queryForSong, queryForDecade, etc are using await, but the mysql driver query function accepts a callback and does not return a promise. Also, the return inside your callback is scoped within the callback, not scoped with respect to your outside async function. Instead wrap the function within a promise, resolve and reject where necessary. Also avoid using await unless absolutely necessary because using it blocks the event loop. I have given an example for how to wrap with promise for queryForSong
function queryForSong(){
return new Promise((resolve, reject) => {
let songGenre;
const getSongGenre = "The queries aren't important, I know they work as expected";
mysqlConnection.query(getSongGenre, (err, rows) => {
if (err) reject(err);
else {
songGenre = row[0].song_genre;
const songSearchQuery = "SELECT query";
mysqlConnection.query(songSearchQuery, (err, rows) => {
if (err) reject(err);
else
resolve(saveResults(rows));
});
}
});
});
}
So it turns out it was the saveResults function causing me issues. I don't totally understand why this would fix it, but instead of resolving to the value of the saveResults function, I instead tried just including the saveResults body in each function that returns a promise and that works. For example,
function queryForSong() {
var songResultsArray = [];
var songGenre;
getSongGenre = "SELECT query";
return new Promise(function (resolve, reject) {
mysqlConnection.query(getSongGenre, (err, rows) => {
if (err) reject(err);
var row = rows[0];
songGenre = row.song_genre;
songSearchQuery = "SELECT query #2";
mysqlConnection.query(songSearchQuery, (err, rows) => {
if (err) throw err;
//this is the body of the saveResults function
Object.keys(rows).forEach(function (key) {
var row = rows[key];
var song = row.song_name;
songResultsArray.push(song);
});
resolve(songResultsArray);
});
});
});
}
I am trying to call two functions and pass the output of the first function as a parameter into the second.
Function 1:
module.exports.getAllStatisticsByUserId = function(id, callback){
User.findById(id, (err, user) =>{
if(err)
throw err;
if(user)
callback(null, user.statistics);
});
}
Function 2:
module.exports.getGameByStatisticsId = function(id, callback){
Statistics.findById(id, (err, statistics) =>{
if(err)
throw err;
if(statistics)
callback(null, statistics.game);
});
};
I am trying to execute the second method by passing the output of the first method as a parameter but the asynchronous nature of javascript is messing it up. I have tried implementing promises to no avail.
Can anyone suggest some good javascript practices to deal with calling functions asynchronously when they need each other? Any help would be appreciated.
After fixing the issue I mentioned above, you can call them in sequence like this:
module.exports.getAllStatisticsByUserId = function(id, callback){
User.findById(id, (err, user) =>{
if(err) callback(err);
if(user) callback(null, user.statistics);
});
};
module.exports.getGameByStatisticsId = function(id, callback){
Statistics.findById(id, (err, statistics) =>{
if(err) callback(err);
if(statistics) callback(null, statistics.game);
});
};
someService.getAllStatisticsByUserId(id, (err, statistics) => {
if (err || !statistics) {
// handle error
return;
}
someService.getGameByStatisticsId(statistics.id, (err, game) => {
if (err || !game) {
// handle error
return;
}
// handle game
});
});
However, as noted in Mongoose documentation:
When a callback function is not passed, an instance of Query is returned, which provides a special query builder interface.
A Query has a .then() function, and thus can be used as a promise.
So you can simply rewrite the calls like this:
someService.getAllStatisticsByUserId(id).then(statistics =>
someService.getGameByStatisticsId(statistics.id)
).then(game => {
// handle game
}).catch(err => {
// handle error
});
or convert it into an async/await function:
async function getGameByUserId(id) {
try {
const statistics = await someService.getAllStatisticsByUserId(id);
const game = await someService.getGameByStatisticsId(statistics.id);
// handle game
} catch (error) {
// handle error
}
}
Note that an async function always returns a Promise, so you must await it or chain it with a .then() to ensure completion of the query and resolve the returned value, if any.
It looks like you should be able to write:
getAllStatisticsByUserId("me", (err, stats) => {
getGameByStatisticsId(stats.id, (err, game) => {
console.log(game);
});
});
Here's how it would look if these functions returned promises instead.
getAllStatisticsByUserId("me")
.then(stats => getGameByStatisticsId(stats.id))
.then(game => console.log(game))
Even better, if you're able to use a version of Node that supports async/await then you could write.
let stats = await getAllStatisticsByUserId("me");
let game = await getGameByStatisticsId(stats.id);
console.log(game);
This would mean slightly rewriting the original functions (unless User.findById and Statistics.findById already return promises).
module.exports.getAllStatisticsByUserId = function(id, callback){
return new Promise((resolve, reject) => {
User.findById(id, (err, user) =>{
if(err) return reject(err);
return resolve(user.statistics);
});
});
}