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 });
Related
Reading
https://www.twilio.com/blog/implementing-programmable-chat-php-laravel-vue-js
I try implement chat in my Laravel 8 / jQuery 3.5.1 / vue 2.6 app.
This docs has defined :
setupChannel(channel){
let vm = this;
return this.leaveCurrentChannel()
.then(function() {
return vm.initChannel(channel);
})
.then(function(_channel) {
return vm.joinChannel(_channel);
})
.then(this.initChannelEvents);
},
I want to extend joinChannel method, as I want to make checks if current logged user (laravel auth)
is already joined. I try to make it with promise and failes, as code inside of
vm.tc.messagingClient.getSubscribedUsers() is not run. I do
setupChannel(channel){
let vm = this;
return this.leaveCurrentChannel()
.then(function() {
return vm.initChannel(channel);
})
.then(function(_channel) {
let promise = new Promise((resolve, reject) => {
// debugger
vm.tc.messagingClient.getSubscribedUsers().then(function(users) {
// THESE CODE IS NOT RUN. If to uncomment console and debugging it is not triggered
// console.log('++ users::')
// console.log(users)
// debugger
for (let i = 0; i < users.length; i++) {
const user = users[i];
console.log('user.identity: ' + JSON.stringify(user.identity) );
// console.log('user: ' + JSON.stringify(user, null, 2) );
if( user.identity === vm.loggedUser.name ) {
resolve("result")
}
}
debugger // THESE CODE IS NOT RUN
resolve("error")
})
})
console.log('++ promise::')
console.log(promise) // I SEE this promise in pending state
promise
.then(
result => {
alert("result: " + result);
return _channel;
},
error => {
alert("error: " + error);
return vm.joinChannel(_channel);
}
)
// return vm.joinChannel(_channel);
})
.then(this.initChannelEvents);
If to run code
vm.tc.messagingClient.getSubscribedUsers().then(function(users)
...
inside of promise, it works ok and I got valid results.
What is wrong in my promise structure and how can I fix it?
MODIFIED BLOCK:
I try to follow your way with :
joinGeneralChannel() {
console.log('Attempting to join "general" chat channel...');
let vm = this;
if (this.tc.generalChannel == null) {
console.log('SETTING this.tc.messagingClient.createChannel')
vm.loadChannelList(vm.joinGeneralChannel);
}else {
// console.log('Found general channel:');
this.setupChannel(this.tc.generalChannel);
}
},
async setupChannel(channel) {
let vm = this
await this.leaveCurrentChannel()
const newChannel = await vm.initChannel(channel)
const subscribedUsers = vm.tc.messagingClient.getSubscribedUsers()
console.log('subscribedUsers::')
console.log(subscribedUsers)
let isUserJoined = false
for (let i = 0; i < subscribedUsers.length; i++) {
console.log('subscribedUsers[i] ' + JSON.stringify(subscribedUsers[i]) );
if( subscribedUsers[i].name === vm.loggedUser.name ) {
isUserJoined = true``
break
}
}
debugger
console.log('isUserJoined::')
console.log(isUserJoined)
But in the cosole of my browser I see :
Initialized channel General Channel
TeamChat.vue?e1c8:561 subscribedUsers::
TeamChat.vue?e1c8:562 Promise {<pending>}__proto__: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined
TeamChat.vue?e1c8:573 isUserJoined::
looks like method getSubscribedUsers is asynchronous ?
Thanks!
Probably your Promise fails, that's why then() will never execute. To extend joinChannel method you can do something like this with async/await and ES6 syntax:
async setupChannel(channel) {
let vm = this;
try {
await this.leaveCurrentChannel();
const newChannel = await vm.initChannel(channel);
const users = await vm.tc.messagingClient.getSubscribedUsers();
const isUserJoined = users.some(({ name }) => name === vm.loggedUser.name);
const joinedChannel = isUserJoined ? newChannel : vm.joinChannel(_channel);
return this.initChannelEvents(joinedChannel);
} catch(err) {
// if some of promises below will fail, here you'll see details
console.log('Issue details here:', err);
}
}
I am using postman to call api. I am trying to read google spread sheet row using node js. Response is printing on console but its not returning to postman.
index.js file
app.post('/myapi/getClientkey',async (req, res) => {
var response = null;
console.log("Inside myapi");
try {
response = await spreadsheet.getRecord();
} catch(err){
}
res.send(response);
});
spreadsheet.js
var config = require('./config.json');
var GoogleSpreadsheet = require('google-spreadsheet');
var creds = {
client_email: config.client_email,
private_key: config.private_key
}
var doc = new GoogleSpreadsheet(config.GOOGLE_SHEET_ID)
var sheet = null;
exports.getRecord = async function () {
console.log('Inside - getRecord');
var name = null;
var jsonObj = {};
try {
doc.useServiceAccountAuth(creds, async function (err) {
// Get all of the rows from the spreadsheet.
await doc.getRows(1, async function (err, rows) {
if(rows != null){
var oneRow = rows[0];
name = oneRow.name;
console.log("name :"+name);
jsonObj.client_name = name.toString();
}
console.log(jsonObj);
});
});
}catch(err){
console.log("err :"+err);
}
return jsonObj;
};
How to wait till response is returned from getRecord Function
Move res.send(response); inside try block and read about how to return response from an async call.
Also return jsonObj should be inside try block
I used promise call now it working.
exports.getRecord = async function () {
console.log('Inside - getRecord');
var name = null;
return new Promise( async function(resolve,reject){
try {
var jsonObj = {};
doc.useServiceAccountAuth(creds, async function (err) {
// Get all of the rows from the spreadsheet.
await doc.getRows(1, async function (err, rows) {
if(rows != null){
var oneRow = rows[0];
name = oneRow.name;
console.log("name :"+name);
jsonObj.client_name = name.toString();
}
console.log(jsonObj);
resolve(jsonObj);
});
});
}catch(err){
console.log("err :"+err);
reject();
}
}); // end of promise
};
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");
}
I am using node 10+, and I have this function where I do a database query and wait for the result and return it:
var test3 = (req,res,query) => {
var conn = new sql.ConnectionPool(dbconfig);
var req = new sql.Request(conn);
var result;
return conn.connect().then(async() => {
result = await req.query(query);
conn.close();
return result;
}).catch(e => {
return e;
}).finally(() => {
conn.close();
});
}
First, I would like to know why I have to return the conn.connect() block..
return conn.connect().then(async() => {...
I know it has something to do with promise chaining I think, but I dont understand why, because my async db call is already resolved from the await dbcall function... and I just return the result from inside the function
Then, I have a router where I call the api function here:
router.get("/api/compareCount", function(req,res) {
var query = `SELECT COUNT(*) as count
FROM [DublettenReferenzmenge].[dbo].[DocumentForElasticsearch] where lastChange < dateadd(day,-1,getdate())`;
var query2 = `SELECT COUNT(*) as count
FROM [DublettenReferenzmenge].[dbo].[DocumentForElasticsearch] where lastChange < dateadd(hour,-8,getdate())`;
var query3 =`SELECT COUNT(*) as count
FROM [DublettenReferenzmenge].[dbo].[DocumentForElasticsearch]`;
axios.all([searchES(req,res), test3(req,res,query), test3(req,res,query2) , test3(req,res,query3)])
.then(axios.spread(function (esCount, mssqlCount1, mssqlCount2, mssqlCount3) {
totalES = esCount.hits.total;
totalMSSQL = mssqlCount1.recordset[0].count;
totalMSSQL2 = mssqlCount2.recordset[0].count;
totalMSSQL3 = mssqlCount3.recordset[0].count;totalMSSQL, " mssqlCount2: ", totalMSSQL2, "mssqlCount3: ", totalMSSQL3);
var msg = "ES Dokumente total: " + totalES + " MSSQL Dokumente total: " + totalMSSQL + "<br>";
if ( totalES != totalMSSQL) {
msg += "Critical: " + totalES != totalMSSQL + "<br>";
} if ((totalES != totalMSSQL2)) {
msg += "Warning: " + (totalES != totalMSSQL2) + "<br>";
} if ((totalES > totalMSSQL3)) {
msg += "Achtung es gibt ungelöschte Dokumente im Elasticsearch Index!";
}
res.set('Content-Type', 'text/html');
res.send(msg);
})).catch((err) => {
res.send(err);
});
})
router.get("/api/test3", async function (req,res) {
var query = `SELECT COUNT(*) as count
FROM [DublettenReferenzmenge].[dbo].[DocumentForElasticsearch] where lastChange < dateadd(day,-1,getdate())`;
var result = await test3(req,res,query);
res.json(result);
})
The api/test3 route returns me the result as usual, but the api/compareCount does return me correct results as well...
Furthermore, I have to use the async function ... await test3(..) async-await syntax structure to resolve my result into a variable... But I do not have to use that same structure for my api/compareCount function above, the result is returned anyways in the .then(axios.spread(function(...))). Why is that? I am quite confused as I don't really know the inner workings of the Promise chaining and calls...
EDIT: before my test3() function, I had something like this:
async function testQuery(query) {
try {
let pool = await sql.connect(dbconfig);
let result1 = await pool.request()
//.input('input_parameter', sql.Int, value)
.query(query);
sql.close();
return result1;
} catch (err) {
console.log(err);
sql.close();
} finally {
sql.close();
}
};
I got also results with that function, however, I got some kind of race condition where it told me that the sql- connection already exists, and do sql.close() first if I reload the page too quickly... I dont get this with the test3() function anymore...
To start with, test3() needs to return a promise so the caller knows when it's done and whether it had an error or not. It's probably easiest to use async/await for that:
async function test3(query) => {
const conn = new sql.ConnectionPool(dbconfig);
const request = new sql.Request(conn);
await conn.connect();
try {
const result = await request.query(query);
return result;
} finally {
conn.close();
}
}
Various changes:
Only pass arguments that are going to be used
Use await to simplify async logic flow
Use try/finally to catch any error after connected so we can always close the connection and not leak a connection
Switch from var to const.
Make function async so it returns a promise that is hooked to when the internals are done or have an error
Then, if you adjust how test3() is called to only pass the query argument (since that's all that is used), your use of that function the other places you are using it should work.
Your code was doing this:
var result = await test3(req,res,query);
But, test3() had no return value so the await had nothing useful to do. await works with a promise and you weren't returning a promise that was linked to the async operations inside of test3(). That's what my changes above do.
I stuck by looping through an array that receive values from a promise and push values into a new array which is available outside the foreach.
What i have:
app.post('/submit', function (req, res) {
uploadPics(req, res, function (err) {
if (err instanceof multer.MulterError) {
res.send(JSON.stringify({UploadResult: err.message}));
console.log(err.message + ' ' +'Redirect /home');
} else if (err) {
console.log(err);
} else {
res.send(JSON.stringify({UploadResult: 'Success'}));
var filesarray = req.files;
var picinfos = [];
filesarray.forEach(function(file){
GetFileMetaInfo.filemetainfo(file.path).then(function (metadata){
//Stuck here! Can push values into an array (picinfos) but only available in the foreach. not outside..
})
})
//I need picinfos array here....
}
})
})
How i receive my metadata:
var exif = require('exif-parser');
var fs = require('fs');
exports.filemetainfo = function (filepath) {
return new Promise((resolve) => {
var file = filepath;
var buffer = fs.readFileSync(file);
var parser = exif.create(buffer);
var result = parser.parse();
resolve (result);
}).then(function (metadata){
if (metadata.tags.CreateDate !== undefined){
date = new Date (metadata.tags.CreateDate*1000);
datevalues = [
date.getFullYear(),
date.getMonth()+1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
];
CreateDate = date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate();
CreateTime = date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();
console.log("CrDate:" +CreateDate, "CrTime:" +CreateTime );
} else {
console.log("No Metadata Creation Infos found in " +filepath);
CreateDate = "";
CretaeTime = "";
}
if (metadata.tags.GPSLatitude !== undefined){
GPSLat = metadata.tags.GPSLatitude;
GPSLon = metadata.tags.GPSLongitude;
console.log("GPSLat:" + GPSLat , "GPSLon:" +GPSLon);
}
else {
console.log("No Metadata GPS Infos found in " +filepath)
GPSLat = "";
GPSLon = "";
}
return MetaData = {
GPSLat: GPSLat ,
GPSLon: GPSLon,
CreateDate: CreateDate,
CreateTime: CreateTime,
}
})
}
May i ask someone to give a hand. How can i make my array available outside the foreach. thank you very much!
The reason you're getting empty array at the end of forEach is because, GetFileMetaInfo.filemetainfo() returns a promise and forEach won't wait for async actions.
You could use async/await with for...of loop to get your desired result.
app.post('/submit', function (req, res) {
uploadPics(req, res, async function (err) { // note async here
if (err instanceof multer.MulterError) {
res.send(JSON.stringify({UploadResult: err.message}));
console.log(err.message + ' ' +'Redirect /home');
} else if (err) {
console.log(err);
} else {
res.send(JSON.stringify({UploadResult: 'Success'}));
var filesarray = req.files;
var picinfos = [];
for(let file of filesarray) {
const metadata = await GetFileMetaInfo.filemetainfo(file.path);
// push metadata into your array here
picinfos.push(metadata);
}
// You will have picinfos here
}
})
})
Although the question is already answered by Dinesh Pandiyan there are still some adjustments that can be made. The following code in his answer runs sequential, meaning that every async request is made after the previously returned result is resolved.
for(let file of filesarray) {
const metadata = await GetFileMetaInfo.filemetainfo(file.path);
// ^- pauses the execution of the current running code
// push metadata into your array here
picinfos.push(metadata);
}
async call #1 ╌╌await╌╌> async call #2 ╌╌await╌╌> async call #3 ╌╌await╌╌> result
You could make the code concurrent by first executing all async statements and then wait until all results are resolved. This can be done by simply changing the following:
// execute all the async functions first, reducing the wait time
for(let file of filesarray) {
const metadata = GetFileMetaInfo.filemetainfo(file.path);
// ^- remove the await
// push metadata into your array here
picinfos.push(metadata);
}
// wait for all results to be resolved
picinfos = await Promise.all(picinfos);
// ^- instead await here
async call #1 ╌╌┐
async call #2 ╌╌┼╌╌await all╌╌> result
async call #3 ╌╌┘
The above could be further simplified by simply using an Array.map() in combination with the already shown Promise.all().
var filesarray = req.files;
var picinfos = await Promise.all(filesarray.map(file => {
return GetFileMetaInfo.filemetainfo(file.path);
}));
// picinfos should be present
Or if you want to avoid working with async/await:
var filesarray = req.files;
Promise.all(filesarray.map(file => {
return GetFileMetaInfo.filemetainfo(file.path);
})).then(picinfos => {
// picinfos should be present
});