Trouble with Async/Await with mySQL result in Node - javascript

I have the following code, which is an API call through Express. I need to return an array of quotes, and an array of the parts associated with those quotes. To do so, I'm using async/await, which is working properly in my quotes variable, but I'm getting an empty array in my parts variable.
I believe this has to do with the parts results being pending promises, because when I console log my results within the loop, I get the appropriate values.
I suspect I must be misunderstanding how either async/await works, or how promises/Promise.all works.
Any help understanding why I'm not getting fulfilled promise results is appreciated!
router.get('/:pagenum/:dealerno/:modelyear/', async (req, res) => {
let quotes = await getQuotes(
req.params.pagenum,
req.params.dealerno,
req.params.modelyear
);
console.log('MY QUOTES:');
console.log(quotes); //This returns an array of my quotes, as expected
let parts = await getQuoteParts(quotes);
console.log('MY PARTS:');
console.log(parts); //This returns an array of undefined values
});
function getQuotes(pagenum, dealerno, modelyear) {
// return new Promise(function (resolve, reject) {
var q_quote =
"SELECT quote.id, DATE_FORMAT(quote.timestamp, '%b %d, %Y') AS timestamp, quote.quotenum, quote.quotetype, quote.configtype, quote.sid, quote.tractortype, customer.cus_id, customer.customername, parts.partno, units.type AS fmtype, combos.type AS mmtype FROM GH_savedquoteinfo AS quote JOIN GH_internal_customers AS customer ON quote.cusid=customer.cus_id JOIN GH_savedquoteparts AS parts ON (quote.quotenum = parts.quotenum AND parts.type='unit') LEFT JOIN " +
modelyear +
"_fm_units AS units ON (parts.partno = units.model_no AND quote.tractortype='FM') LEFT JOIN " +
modelyear +
"_mm_combos AS combos ON (parts.partno = combos.model_no AND quote.tractortype='MM') WHERE quote.dealerno=? AND quote.modelyear=? AND quote.quotetype!='multi' ORDER BY quote.quotenum LIMIT 7 OFFSET 0;";
let resultHolder = [];
return new Promise((resolve, reject) => {
connection.query(q_quote, [dealerno, modelyear], function (
err_quote,
result_quote
) {
if (err_quote) {
console.log('error');
} else {
try {
resultHolder.push(result_quote);
} catch (e) {
console.log('error');
} finally {
resolve(resultHolder);
}
}
});
});
}
async function getQuoteParts(quoteResult) {
let quoteParts = [];
let quotePartsResult = await Promise.all(
quoteResult[0].map(async (quote, index) => {
var q_parts =
"SELECT * FROM GH_savedquoteparts WHERE quotenum=? AND (type='' OR type='pvcomplete') ORDER BY id";
connection.query(q_parts, [quote.quotenum], function (
err_parts,
result_parts
) {
if (err_parts) {
console.log('error in parts' + err.message);
} else {
console.log(result_parts); //This returns an array of my parts
quoteParts.push(result_parts);
}
});
})
);
return quoteParts;
}

The reason you're getting an array of undefined values is because you're returning quotePartsResult instead of quoteParts in which you're pushing values. Since you're running a .map on quoteResult and not returning anything inside the map function, it resolves to undefined.
I would like to refactor your code for a better understanding. You should have an array of promises in your Promise.all() call.
Something like:
const promises = qouteResult.map(async (quote) => {
// Your query calls. You can also promisify the callbacks in here using
util.promisify()
});
OR
const promises = qouteResult.map(qoute => {
return new Promise((resolve,reject) => {
// Your query calls.
if(success) {
resolve()
} else {
reject()
}
});
});
const result = await Promise.all(promises);
return result;

As someone in the comments mentioned, the main thing I was missing was a new Promise wrapper around my second connection. I also needed a Promise.all within that second function, getQuoteParts. Here's what the final code looked like:
router.get('/:pagenum/:dealerno/:modelyear/', async (req, res) => {
//QUOTES ARRAY
const quotes = await getQuotes(
req.params.pagenum,
req.params.dealerno,
req.params.modelyear
);
const parts = await getQuoteParts(quotes);
const details = await getPartDescrips(parts, quotes);
Promise.all([quotes, parts, details]).then((results) => {
res.send(results);
});
});
function getQuotes(pagenum, dealerno, modelyear) {
// return new Promise(function (resolve, reject) {
const qQuote =
"SELECT quote.id, DATE_FORMAT(quote.timestamp, '%b %d, %Y') AS timestamp, quote.quotenum, quote.quotetype, quote.configtype, quote.sid, quote.tractortype, customer.cus_id, customer.customername, parts.partno, units.type AS fmtype, combos.type AS mmtype FROM GH_savedquoteinfo AS quote JOIN GH_internal_customers AS customer ON quote.cusid=customer.cus_id JOIN GH_savedquoteparts AS parts ON (quote.quotenum = parts.quotenum AND parts.type='unit') LEFT JOIN " +
modelyear +
"_fm_units AS units ON (parts.partno = units.model_no AND quote.tractortype='FM') LEFT JOIN " +
modelyear +
"_mm_combos AS combos ON (parts.partno = combos.model_no AND quote.tractortype='MM') WHERE quote.dealerno=? AND quote.modelyear=? AND quote.quotetype!='multi' ORDER BY quote.quotenum DESC LIMIT 7 OFFSET 0;";
let resultHolder = [];
return new Promise((resolve, reject) => {
connection.query(qQuote, [dealerno, modelyear], function (
errQuote,
resultQuote
) {
if (errQuote) {
console.log(errQuote.message);
} else {
try {
resolve(resultQuote);
} catch (e) {
console.log(e.message);
}
}
});
});
}
function getQuoteParts(quoteResult) {
var partResults = quoteResult.map((quote, index) => {
return new Promise((resolve, reject) => {
const qParts =
"SELECT * FROM GH_savedquoteparts WHERE quotenum=? AND (type='' OR type='pvcomplete') ORDER BY type='pvcomplete', type='', id";
// new Promise((resolve, reject) => {
connection.query(qParts, [quote.quotenum], function (
errParts,
resultParts
) {
if (errParts) {
console.log('error in PARTS');
} else {
resolve(resultParts);
}
});
});
});
return Promise.all(partResults);
}
I wrote an article outlining how it works here:
https://medium.com/#amymurphy_40966/node-mysql-chained-promises-vs-async-await-9d0c8e8f5ee1

Related

Callback function inside for loop - Nodejs

Hope you're having a good day. I recently discovered that it is not as easy to handle callbacks inside a for loop. I have tried a few things but couldn't find a solution.
Here is the code:
var book = new Array;
var chapters = Object.keys(epub.spine.contents).length;
for (let i = 0; i < chapters; i++) {
let cacheArray = [];
epub.getChapter(epub.spine.contents[i].id, function (err, data) {
if (err) {
return console.log(err);
}
//remove html tags
let str = data.replace(/<\/?[\w\s]*>|<.+[\W]>/g, '');
book.push(str)
})
}
console.log(book)//returns empty array ofc
After this code is executed, I need to loop over the array to search its contents. If that was not the case, my approach would be to just send it to a db.
My approach was the following:
var recursiveChapter = function (n) {
var book = new Array;
if (n < chapters) {
// chapter function
epub.getChapter(epub.spine.contents[n].id, function (err, data) {
if (err) {
throw err
}
//remove HTML tags
let str = data.replace(/<\/?[\w\s]*>|<.+[\W]>/g, '');
book.push(str)
recursiveChapter(n + 1)
});
}
}
//start the recursive function
recursiveChapter(0)
console.log(book)//returns an empty array
I am stuck and can't think of a way of using this data without storing it in a db.
Any help would be appreciated .
There are a few ways you can tackle this. One way is to use the async library, this allows to to run async. calls in parallel, or in series.
For example, we can use async.mapSeries() to get the result of a series of asynchronous calls as an array.
You input your array of ids, then the callback will return an array of the data returned, for example, I've mocked out the getChapter() function to give you an idea of how it would work:
// Mock out epub object
const epub = {
getChapter(id, callback) {
setTimeout(() => callback(null, "Data for id " + id), 250);
}
}
let ids = [1,2,3,4];
console.log("Calling async.mapSeries for ids:", ids);
async.mapSeries(ids, epub.getChapter, (err, result) => {
if (err) {
console.error(err);
} else {
console.log("Result:", result)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/async/3.2.0/async.min.js" integrity="sha512-6K6+H87tLdCWvY5ml9ZQXLRlPlDEt8uXmtELhuJRgFyEDv6JvndWHg3jadJuBVGPEhhA2AAt+ROMC2V7EvTIWw==" crossorigin="anonymous"></script>
You could also promisify the epub call and use Promise.all to get the result, like so:
epub = {
getChapter(id, callback) {
setTimeout(() => callback(null, "Data for id " + id), 250);
}
}
let ids = [1,2,3,4];
function getChapterPromisified(id) {
return new Promise((resolve, reject) => {
epub.getChapter(id, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
})
}
// Create a list of promises, one for each call
const promises = ids.map(id => getChapterPromisified(id))
Promise.all(promises)
.then(result => console.log("Result:", result))
.catch(error => console.error("An error occurred:", err));

Cannot await for sqlite3.Database.get() function completion in Node.js

I'm struggling with some basic async/await problem in node.js using node-sqlite3.
My objective is to select some value from SQLite DB, check it for some condition and take some actions in case the condition is met. Here's the code:
const sqlite3 = require('sqlite3').verbose();
main();
async function main() {
let ordersDb = await createDbConnection('./ProcessedOrders.db');
var orderProcessed = await orderAlreadyProcessed(ordersDb, "555");
console.log("orderProcessed = " + orderProcessed);
if (!orderProcessed) {
console.log("So condition is met!");
}
}
async function orderAlreadyProcessed(ordersDb, orderNumberStr) {
console.log('starting orderAlreadyProcessed function'); //DEBUG
var result;
var query = 'select count(SoldOrderNumber) as "recsCount" from ProcessedSoldOrders where SoldOrderNumber = ?;';
await ordersDb.get(query
,[orderNumberStr]
,(err, row) => {
console.log('Row with count = ' + row); //DEBUG
console.log('row.recsCount = ' + row.recsCount); //DEBUG
result = typeof row !== 'undefined' && row.recsCount > 0;
});
console.log('Returning ' + result); //DEBUG
return result;
}
async function createDbConnection(dbFileName) {
let db = new sqlite3.Database(dbFileName, (err) => {
if (err) {
console.log(err.message);
}
});
return db;
}
But what I get is code executing further, not awaiting for Database.get() method at all! As a result, here's what I see printing in console:
starting orderAlreadyProcessed function
Returning undefined
orderProcessed = undefined
So IF condition met!
Row with count = [object Object]
row.recsCount = 1
As we can see, we return from orderAlreadyProcessed too early with return value = 'undefined'. So condition is met, actions taken, and only then Database.get() returns. But if it was properly awaited, condition would not be met.
How can I make it await for result value?
Since you want to use async/await, and the node-sqlite3 (sqlite3) library does not support the Promise API, you need to use the node-sqlite (sqlite) library, which is a wrapper over sqlite3 and adds support for the Promise API. Then, your code will look something like this:
const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
async function main() {
try {
sqlite3.verbose();
const ordersDb = await createDbConnection('./ProcessedOrders.db');
const orderProcessed = await orderAlreadyProcessed(ordersDb, "555");
console.log("orderProcessed = " + orderProcessed);
if (!orderProcessed) {
console.log("So condition is met!");
}
} catch (error) {
console.error(error);
}
}
async function orderAlreadyProcessed(ordersDb, orderNumberStr) {
try {
console.log('Starting orderAlreadyProcessed function');
const query = 'SELECT COUNT(SoldOrderNumber) as `recsCount` from ProcessedSoldOrders where SoldOrderNumber = ?;'
const row = await ordersDb.get(query, [orderNumberStr]);
console.log('Row with count =', row);
console.log('row.recsCount =', row.recsCount);
const result = typeof row !== 'undefined' && row.recsCount > 0;
console.log('Returning ' + result);
return result;
} catch (error) {
console.error(error);
throw error;
}
}
function createDbConnection(filename) {
return open({
filename,
driver: sqlite3.Database
});
}
main();
I specifically did not remove your console.log and other parts of the code so as not to confuse the original logic of your program.
If we don't to use another library
then we can return a new Promise function & use await, as below:
Note: Below has example for INSERT/run, instead of SELECT/get, but promise/await works same
const sqlite3 = require("sqlite3").verbose();
let db;
db = new sqlite3.Database('./Chinook.db');
function insert() {
return new Promise((resolve, reject) => { // return new Promise here <---
const userId = uuid4();
let sql = `INSERT INTO Users(id) VALUES (?)`; // INSERT <----
let params = [userId];
return db.run(sql, params, function (err, res) { // .run <----
if (err) {
console.error("DB Error: Insert failed: ", err.message);
return reject(err.message);
}
return resolve("done");
});
});
}
let result = await insert(); // now await works fine <------
res.send({ result });

Async functions in for loops javascript

I am not experienced with async functions and I would like to perform a request in a for loop. Here's my code:
app.route('/friendlist').post((req, res) => {
var body = req.body;
var list = "";
con.query(`SELECT * FROM player_friends WHERE main_user_id = '${body.player_id}'`, (err, row, fields) => {
if (err) throw err;
async function queryOutUserData(data) {
var rows = await new Promise((resolve, reject) => {
con.query(`SELECT * FROM players WHERE player_id = '${data.player_id}'`, (error, player, field) => {
if (error) {
console.log(error);
return reject(error);
}
resolve(player);
});
});
rows.then(message => {
return message
});
}
for (var i = 0; i <= row.length; i++) {
console.log(row[i].main_user_id);
var result = await queryOutUserData(row[i]);
list = list + ";" + result[0].player_id + ":" + result[0].player_username;
}
console.log(list);
return list;
});
});
Actually here's the full problem: I did some debugging and apparently value i in for loop increases before the promise is resolved. Also as I mentioned I am not familiar with async functions, could you provide me a descriptive resource about how promises and async functions work?
Thanks
NOTE: For better indentation, here's the code: https://hastebin.com/fovayucodi.js
Instead of using async/await I suggest doing everything in one query using WHERE IN rather than one query per player. See if the following fits your needs:
app.route('/friendlist').post((req,res) => {
var body = req.body;
var list = "";
con.query(`SELECT * FROM player_friends WHERE main_user_id = '${body.player_id}'`, (err, row, fields) => {
if (err) throw err;
const playerIds = row.map(player => player.player_id);
con.query(`SELECT * FROM players WHERE player_id IN ${playerIds}`, (error, players, field) => {
for (let player of players) {
list += `;${player.player_id}:${player.player_username}`;
}
});
console.log(list);
return list;
});
});
If you await a promise, it evaluates to the result of that promise, so rows is not a promise, it's the result. So this:
rows.then(message => {return message});
Doesn't make much sense, just do:
return message;
Also, you have an await inside of a regular function, thats a syntax error.
Additionally return list; doesn't do much (if that is express), you might want to return res.json({ list });.
: I did some debugging and apparently value i in for loop increases before the promise is resolved.
I doubt that you can debug code if you can't actually run it because of the syntax errors.
try to use for-of instead just a for.
something like this:
Async Function:
async function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
return
}, 1000)
})
}
Here another function using for and waiting for the finish of loop
async function myFunction() {
const data = [1,2,3,4,5,6,7,8]
for(let i of data) {
const value = await test();
console.log(value)
}
console.log("finish");
}

Can we make promise to wait until resolved and onreject call back the promise again

I'm learning about Promise's and have a little doubt assuming that I want to get resolved status out of Promises
and not want reject! Can I just call back the promise function inside
catch to make sure that I get only approved value! Is that possible or
will it throw an error or goes to loop iteration
let promisetocleantheroom = new Promise(function cleanroom(resolve, reject) {
//code to clean the room
//then as a result the clean variable will have true or flase
if (clean == "true") {
resolve("cleaned");
} else {
reject("not cleaned");
}
});
promisetocleantheroom.then(function cleanroom(fromResolve) {
// wait for the function to finish only then it would run the function then
console.log("the room is " + fromResolve);
}).catch(function cleanroom(fromReject) {
//calling back the promise again
cleanroom();
});
If you don't mind having higher order functions and recursivity, here is my proposed solution.
First you need to wrap your promise in a function to recreate it when it fails. Then you can pass it to retryPromiseMaker with a partial error handler to create another function that will act as retrier. And this function will return a Promise that will fulfill only if one of the inner promises fulfills.
Sounds complicated but I promise you it is not!
const retryPromiseMaker = (fn, errorfn = null) => {
const retryPromise = (retries = 3, err = null) => {
if (err) {
errorfn(err);
}
if (retries === 0) {
return Promise.reject(err);
}
return fn()
.catch(err => retryPromise(retries - 1, err));
};
return retryPromise;
}
const cleanTheRoom = (resolve, reject) => {
// simulate cleaning as a probability of 33%
const clean = Math.random() < 0.33;
setTimeout(() => {
if (clean) {
resolve("cleaned");
} else {
reject("not cleaned");
}
}, Math.random() * 700 + 200);
};
const promiseToCleanTheRoom = () => new Promise(cleanTheRoom);
const logStatus = end => value => {
let text = '';
if (end){
text += "at the end ";
}
text += "the room is " + value;
console.log(text);
};
retryPromiseMaker(promiseToCleanTheRoom, logStatus(false))(4)
.then(logStatus(true),logStatus(true));

Collecting paginated data of unknown size using promises

I'm querying a REST-API to get all groups. Those groups come in batches of 50. I would like to collect all of them before continuing to process them.
Up until now I relied on callbacks but I'd like to use promises to chain the retrieval of all groups and then process the result-array further.
I just don't quite get how to replace the recursive functional call using promises.
How would I use A+ promises to escape the callback hell I create with this code?
function addToGroups() {
var results = []
collectGroups(0)
function collectGroups(offset){
//async API call
sc.get('/tracks/'+ CURRENT_TRACK_ID +'/groups?limit=50&offset=' + offset , OAUTH_TOKEN, function(error, data){
if (data.length > 0){
results.push(data)
// keep requesting new groups
collectGroups(offset + 50)
}
// finished
else {
//finish promise
}
})
}
}
Using standard promises, wrap all of your existing code as shown here:
function addToGroups() {
return new Promise(function(resolve, reject) {
... // your code, mostly as above
});
}
Within your code, call resolve(data) when you're finished, or reject() if for some reason the chain of calls fails.
To make the whole thing more "promise like", first make a function collectGroups return a promise:
function promiseGet(url) {
return new Promise(function(resolve, reject) {
sc.get(url, function(error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
}
}
// NB: promisify-node can do the above for you
function collectGroups(offset, stride) {
return promiseGet('/tracks/'+ CURRENT_TRACK_ID +'/groups?limit=' + stride + '&offset=' + offset , OAUTH_TOKEN);
}
and then use this Promise in your code:
function addToGroups() {
var results = [], stride = 50;
return new Promise(function(resolve, reject) {
(function loop(offset) {
collectGroups(offset, stride).then(function(data) {
if (data.length) {
results.push(data);
loop(offset + stride);
} else {
resolve(data);
}
}).catch(reject);
)(0);
});
}
This could work. I am using https://github.com/kriskowal/q promises.
var Q = require('q');
function addToGroups() {
var results = []
//offsets hardcoded for example
var a = [0, 51, 101];
var promises = [], results;
a.forEach(function(offset){
promises.push(collectGroups(offset));
})
Q.allSettled(promises).then(function(){
promises.forEach(function(promise, index){
if(promise.state === 'fulfilled') {
/* you can use results.concatenate if you know promise.value (data returned by the api)
is an array */
//you also could check offset.length > 0 (as per your code)
results.concatenate(promise.value);
/*
... do your thing with results ...
*/
}
else {
console.log('offset',index, 'failed', promise.reason);
}
});
});
}
function collectGroups(offset){
var def = Q.defer();
//async API call
sc.get('/tracks/'+ CURRENT_TRACK_ID +'/groups?limit=50&offset=' + offset , OAUTH_TOKEN, function(error, data){
if(!err) {
def.resolve(data);
}
else {
def.reject(err);
}
});
return def.promise;
}
Let me know if it works.
Here's complete example, using spex.sequence:
var spex = require("spex")(Promise);
function source(index) {
return new Promise(function (resolve) {
sc.get('/tracks/' + CURRENT_TRACK_ID + '/groups?limit=50&offset=' + index * 50, OAUTH_TOKEN, function (error, data) {
resolve(data.length ? data : undefined);
});
});
}
spex.sequence(source, {track: true})
.then(function (data) {
// data = all the pages returned by the sequence;
});
I don't think it can get simpler than this ;)

Categories

Resources