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);
});
});
});
}
Related
I making use of node.js + mysql in my app. I would like to export an array containing results/rows of an SQL select statement to another javascript file and display the values stored in the array.
stats.js
var phonenum='0718900000'
var sql='SELECT name,surname,age from Tbluser WHERE number=?;'
conn.query(sql,[phonenum],function(err,results){
if (err){
throw err;
};
/*if (results==0)
{
let dealersMessage="❌ User not found"
}*/
console.log(results)
var datastatagentStore=[];
results.forEach(item =>{
module.exports=datastatagentStore.push({
name:item.name,
surname:item.surname,
age:item.age,
})
})
app.js
var{datastatagentStore}=require('../functions/stats.js')
console.log('User details '+datastatagentStore);
Based on a comment above:
My goal is to execute the query and display the results to the user every time the user selects the 'view my details' button.
Then you don't want to export the results, you want to export the function which performs the query. That function would return [a Promise which resolves to] the results.
If conn.query returns a Promise then it might look something like this:
var getUserDetails = function (phonenum) {
var sql='SELECT name,surname,age from Tbluser WHERE number=?;'
return conn.query(sql, [phonenum], function(err, results) {
if (err) {
throw err;
};
console.log(results)
var datastatagentStore = [];
results.forEach(item => {
datastatagentStore.push({
name:item.name,
surname:item.surname,
age:item.age,
})
});
return datastatagentStore;
});
};
module.exports = getUserDetails;
Though I don't know if conn.query does return a Promise. If it doesn't, you can manually create one:
var getUserDetails = function (phonenum) {
return new Promise(function (resolve, reject) {
var sql='SELECT name,surname,age from Tbluser WHERE number=?;'
conn.query(sql, [phonenum], function(err, results) {
if (err) {
reject(err);
};
console.log(results)
var datastatagentStore = [];
results.forEach(item => {
datastatagentStore.push({
name:item.name,
surname:item.surname,
age:item.age,
})
});
resolve(datastatagentStore);
});
});
};
module.exports = getUserDetails;
Note the use of resolve and reject here to manually complete the Promise. But however you structure it, the overall point is that your module export wouldn't be the results of having executed the query, it would be the function to execute the query. And since executing the query is an asynchronous operation, that function should return a Promise.
Then you can use it like any other asynchronous operation:
var datastatagentStore = require('../functions/stats.js');
datastatagentStore('0718900000').then(function (details) {
console.log('User details ', details);
});
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 am just starting to work with JS and SQLite. And could not find any help for my specific question.
I want to keep a return to use it as a foreign key in a second table.
This is my function:
async function getIdbyName(table, row, name) {
let nameNeu = '"' + name + '"';
let sql =
"SELECT id as print FROM " +
table +
" WHERE " +
row +
" = " +
nameNeu +
" LIMIT 1;";
// await db.get(sql, (err, row) => {
// console.log(row.print);
// return row;
// });
return await db.get(sql);
}
getIdbyName("...", "...", "...")
.then(function (value) {
console.log("Success!", value); // <-- prints: Success! undefined
})
.catch(function (err) {
console.log("Caught an error!", err);
});
console.log(getIdbyName("r_Tag", "r_Tag", "test")); //<-- shows me a Promise
What do I have to do so that promise does not stay undefined outside of the function?
Rest of the code:
var sqlite3 = require("sqlite3").verbose();
let db = new sqlite3.Database("./assets/db/test.db", (err) => {
if (err) {
return console.error(err.message);
}
console.log("Connected to the SQlite database.");
});
My other function just creat some strings and I run a few times db.run(...) to add some tables.
To put it more plainly, your getIdByName function never returns anything. You need to return the value you get back from await db.get(...). Once you do that, when you call getIdByName, you should get your response from the database.
You should also know that your code is susceptible to SQL injection, which is a major security vulnerability. Instead of concatenating a string, you should use a prepared statement.
async function getIdbyName(table, row, name) {
return await db.get(sql);
}
Update: Promise Wrapper for SQLlite - Aug 1, 2020
Based on this blog post, it seems it's not possible to do native async/await using sqlite3. However, you can write a wrapper function around db.all to return a promise, which will allow you to use async/await. Note the use of ? in the SQL statement, which will be replaced by the values of the array in the second argument following the same order. For more help with parameterized queries, read the params bullet point in the documentation here.
const sqlite3 = require("sqlite3").verbose();
const db = new sqlite3.Database("./assets/db/test.db", (err) => {
if (err) {
return console.error(err.message);
}
console.log("Connected to the SQlite database.");
});
db.query = function (sql, params = []) {
const that = this;
return new Promise(function (resolve, reject) {
that.all(sql, params, function (error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
async function getIdByName(table, name) {
// assemble sql statement
const sql = `
SELECT id
FROM ?
WHERE name = ?;
`;
return await db.query(sql, [table, name]);
}
// need async to call
(async () => {
const result = await getIdByName('books', 'my_name');
console.log(result);
})();
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);
});
});
}
Is it possible to use javascript promises instead of regular callbacks within CosmosDB (DocumentDB) stored procedure API calls? An usage would be implementing pagination.
For example
token = getToken();
doSomething(token);
//....
function getToken(....) {
//...
collection.queryDocuments(link, query, queryOptions, function(error, documents, responseOptions) {
return responseOptions.continuation;
});
}
would not work because the token is returned within a callback, and the execution continues. Could you please give an example of how you would implement this?
The version of ECMAScript referenced in Cosmos DB docs supports async/await and Promises. I am able to use both of those in my stored procedures.
Here's a function that returns a promise that makes a parameterized document query:
function queryDocumentsAsync(sql, parameters, options) {
const querySpec = {
query: sql,
parameters: parameters
};
return new Promise((resolve, reject)=>{
let isAccepted = __.queryDocuments(__.getSelfLink(), querySpec, options || {}, (err, feed, options) => {
if(err) reject(err);
resolve({
feed: feed,
options: options
});
});
if(!isAccepted) throw "Query was not accepted.";
});
}
I am seeing some limitations around forcing a rollback with this approach, though. If you throw an Error, it gets swallowed by the promise chain and never gets out.
Here's an example on how to use async await for query and replace scenario.
function async_sample() {
const ERROR_CODE = {
NotAccepted: 429
};
const asyncHelper = {
queryDocuments(sqlQuery, options) {
return new Promise((resolve, reject) => {
const isAccepted = __.queryDocuments(__.getSelfLink(), sqlQuery, options, (err, feed, options) => {
if (err) reject(err);
resolve({ feed, options });
});
if (!isAccepted) reject(new Error(ERROR_CODE.NotAccepted, "replaceDocument was not accepted."));
});
},
replaceDocument(doc) {
return new Promise((resolve, reject) => {
const isAccepted = __.replaceDocument(doc._self, doc, (err, result, options) => {
if (err) reject(err);
resolve({ result, options });
});
if (!isAccepted) reject(new Error(ERROR_CODE.NotAccepted, "replaceDocument was not accepted."));
});
}
};
async function main() {
let continuation;
do {
let { feed, options } = await asyncHelper.queryDocuments("SELECT * from c", { continuation });
for (let doc of feed) {
doc.newProp = 1;
await asyncHelper.replaceDocument(doc);
}
continuation = options.continuation;
} while (continuation);
}
main().catch(err => getContext().abort(err));
}
With some cleverness you can use webpack to inline node dependencies, including promisify, which lets you do this:
https://github.com/Oblarg/cosmosdb-storedprocs-ts/blob/master/BuildStoredProcs.js