Issue in promise based recursive function - javascript

I am working on AWS Lambda using nodejs environment. I have one API in which I am using recursive function for some functionality.
Actually most people say avoid recursive function but as per my functionality I need this. My functionality is as follows.
I have one table table1 in which there are two columns pid and cid which is defined as unique constraint. Which means combination of two columns should be unique.
So if I am inserting any combination in table1 and it that combination already exist then it gives me duplicate entry error which is correct as per my functionality.
So in order to handle this duplicate entry error I have used try..catch block. So in catch block I have checked if duplicate entry error occurred then I am calling one recursive function which try different combination until new entry is created in table1.
And my recursive function is promise based. but when new entry in created successfully then I am resolving the promise. But promise does not get returned from where I have called my recursive function for very first time. And because of that timeout occurs.
So please someone suggest me solution so that my promise got resolved and my functionality will continue from point where I have called my recursive function for very first time. So the timeout will not come.
I am providing my code for reference.
var mysql = require('mysql');
var con = mysql.createConnection({
"host": "somehost.com",
"user": "myusername",
"password": "mypassword",
"database": "mydatabase"
});
exports.handler = async (event, context) => {
try {
con.connect();
} catch (error) {
throw error;
return 0;
}
let tableId = '';
let count = '';
try {
var tempUsageData = {
user_id: userId,
code: code,
platform: source,
some_id: some_id,
count: count
};
dbColumns = 'user_id, code, platform, added_on, count, some_id';
let usageData = [
[userId, code, source, new Date(), count, some_id]
];
var tableInsert = await databaseInsert(con, constants.DB_CONSTANTS.DB_USAGE, dbColumns, usageData);
tableId = tableInsert.insertId;
} catch (error) {
console.log('## error insert table1 ##', error);
if (error.errno == 1062) {
try {
// calling recursive function here
let newTableData = await createTableEntry(con, tempUsageData);
tableId = newTableData.new_usage_id;
count = newTableData.new_count;
} catch (error) {
console.log('Error', error);
return 0;
}
} else {
return 0;
}
};
console.log('## EXECUTION DONE ##');
return 1;
}
var createTableEntry = (con, dataObject) => {
return new Promise(async function (resolve, reject) {
console.log('createTableEntry Called for count', dataObject.count);
try {
var newCounter = await getDataFromDatabase(con, dataObject.some_id);
dbColumns = 'user_id, code, platform, added_on, count, some_id';
let tableData = [
[userId, code, source, new Date(), Number(newCounter[0].counter + 1), some_id]
];
var tableInsert = await databaseInsert(con, 'table1', dbColumns, tableData);
let response = {
new_table_id: tableInsert.insertId,
new_count: Number(newCounter[0].counter + 1)
}
return resolve(response);
//function not returning from here once successful entry done and timeout occures
} catch (error) {
console.log('## ERROR ##', error);
if (error.errno == 1062) {
console.log('## CALL FUNCTION AGAIN ##');
dataObject.count = Number(newCounter[0].counter + 1);
await createTableEntry(con, dataObject);
} else {
return reject(error);
}
}
});
};
My final output should be message "EXECUTION DONE" should be displayed once execution done.
Please suggest me good solution for this. Thanks in advance.

Update your catch block
catch (error) {
console.log('## ERROR ##', error);
if (error.errno == 1062) {
console.log('## CALL FUNCTION AGAIN ##');
dataObject.count = Number(newCounter[0].counter + 1);
let result = await createTableEntry(con, dataObject);
return resolve(result);
} else {
return reject(error);
}
}

Related

Why can't I return data after await a promise object in async function

I'm a newbie studying coding.
I tried to mock mysql database query, so I followed below code(https://techhelpnotes.com/node-js-mock-database-jest-using-ts-jest-utils/)
//database.js
const getDbConnection = () => {
const pool = mysql.createPool(DB_CONFIG);
return {
query : function (sql) {
return util.promisify(pool.query).call(pool, sql);
};
//controller.js
try {
let result = await db.query(sql);
res.status(200).json(result);
} catch (error) {
console.log(DB error, error);
}
It worked for me, but I thought I could return the query with await by using it in databse.js like the one below
query : async function(sql) {
try {
return await util.promisify(pool.query).call(pool, sql);
} catch (error) {
console.log(DB error, error);
}
}
so I thought I can use query function without error handling
let result = await db.query(sql);
But it doesn't work. What difference is there between the two codes that makes the above code work and the below now??
Many thanks!

Error "Given transaction number * does not match" in mongodb and nodejs

I want to modify two schema while adding data. For that I used ACID transaction of mongodb with nodejs as follow. But, when I run program it displays the error like
(node:171072) UnhandledPromiseRejectionWarning: MongoError: Given transaction number 3 does not match any in-progress transactions. The active transaction number is 2
at MessageStream.messageHandler (/home/user/Projects/project/node_modules/mongodb/lib/cmap/connection.js:272:20)
at MessageStream.emit (events.js:375:28)
at MessageStream.emit (domain.js:470:12)
addData = async(request: Request, response: Response) => {
const session = await stockSchema.startSession()
try {
const userData = request.body
let data = {}
const transaction = await session.withTransaction(async() => {
try {
userData.products.map(async(item: any) => {
await inventorySchema.findOneAndUpdate({ _id: item.materialID }, { $inc: {qty: -item.qty }}, { session });
});
data = new stockSchema(userData);
await data.save({ session });
} catch (error) {
await session.abortTransaction()
throw new Error("Could not create data. Try again.");
}
});
if (transaction) {
session.endSession()
return returnData(response, data, 'Data created successfully.');
} else {
throw new Error("Could not create data. Try again.");
}
} catch (error: any) {
session.endSession();
return Error(response, error.message, {});
}
}
So you might have figured out the answer to this already, but anyway, after months of frustration, and no clear answer on the internet, i finally figured it out.
The problem with your code above is that you are passing session into a database operation (the .findOneAndUpdate function above) that is running within .map . Meaning, your 'transaction session' is being used concurrently, which is what is causing the error. read this: https://www.mongodb.com/community/forums/t/concurrency-in-mongodb-transactions/14945 (it explains why concurrency with transactions creates a bit of a mess.)
Anyway, instead of .map, use a recursive function that fires each DB operation one after another rather than concurrently, and all your problems will be solved.
You could use a function something like this:
const executeInQueue = async ({
dataAry, //the array that you .map through
callback, //the function that you fire inside .map
idx = 0,
results = [],
}) => {
if (idx === dataAry.length) return results;
//else if idx !== dataAry.length
let d = dataAry[idx];
try {
let result = await callback(d, idx);
results.push(result);
return executeInQueue({
dataAry,
callback,
log,
idx: idx + 1,
results,
});
} catch (err) {
console.log({ err });
return results.push("error");
}
};

Class/ function result use in following code

I am a beginner in javascript and am now trying to understand the subject of classes. I've defined the following class. Now I would like to use the result outside of this in a variable.
The class looks like this:
class Maad{
constructor(name, NumWeek, NumMonth){
this.name =name;
this.NumWeek = NumWeek;
this.NumMonth = NumMonth;
}
queryMaad(){
const mongodb =require('mongodb');
const client = require('mongodb').MongoClient;
const url= 'mongodb://localhost:27017/vertrieb';
client.connect(url,(error, db) =>{
if(!error){
console.log("month_log steht")
};
let col = db.collection("umsatz5");
col.aggregate([{'$match': {'AD': this.name}}, {'$match': {'Kalenderwoche':this.NumWeek}}, {'$count': 'procjetnumber'}],function(err, result){
if (err) {
console.error("Error calling", err);
}
console.log(result[0].projectnumber);
result[0].projectnumber;
})
db.close();
});
}
}
My request is:
let ma_1 = new Maad("Hans Wurst", NumWeek);
ma_1.queryMaad();
How can I save the result (the number of projects) in a variable to use it outside of the class? Thanks for your help.
In general, you would assign it basically the way that you assign anything:
const ma_1 = new Maad("Hans Wurst", NumWeek);
const myVar = ma_1.queryMaad();
However your method is a void method which doesn't return anything, so you need to edit your class if you want to get the number of projects.
Returning something from the function is harder than it sounds because MongoClient.connect is a void method which uses callbacks rather than returning a Promise of a response. Honestly I would recommend using a library like mongoose. But it is possible to make the method asynchronous ourselves by returning a new Promise which we resolve or reject based on the callbacks.
class Maad {
constructor(name, NumWeek, NumMonth) {
this.name = name;
this.NumWeek = NumWeek;
this.NumMonth = NumMonth;
}
async queryMaad() {
const url = "mongodb://localhost:27017/vertrieb";
return new Promise((resolve, reject) => {
MongoClient.connect(url, (error, db) => {
if (error) {
reject(error);
}
console.log("month_log steht");
let col = db.collection("umsatz5");
col.aggregate(
[
{ $match: { AD: this.name } },
{ $match: { Kalenderwoche: this.NumWeek } },
{ $count: "procjetnumber" }
],
function (err, result) {
if (err) {
reject(err);
}
resolve(result);
}
);
db.close();
});
});
}
}
Now queryMaad is an async method, one which returns a Promise. That Promise will resolve to the result of col.aggregate on success (you could also resolve to result[0].projectnumber). The promise will reject if there is an error in the connect or col.aggregate methods.
You now get your value like so (probably inside of a function which is itself async):
const result = await ma_1.queryMaad();
You can catch rejection errors here, or allow them to be thrown and catch them higher up.
try {
const result = await ma_1.queryMaad();
} catch (error) {
// console.error or whatever
}

Async function does not wait for await function to end

i have an async function that do not work as expected, here is the code :
const onCreateCoachSession = async (event, context) => {
const { coachSessionID } = context.params;
let coachSession = event.val();
let opentokSessionId = 'prout';
await opentok.createSession({ mediaMode: 'relayed' }, function(
error,
session
) {
if (error) {
console.log('Error creating session:', error);
} else {
opentokSessionId = session.sessionId;
console.log('opentokSessionIdBefore: ', opentokSessionId);
const sessionId = session.sessionId;
console.log('Session ID: ' + sessionId);
coachSession.tokbox = {
archiving: true,
sessionID: sessionId,
sessionIsCreated: true,
};
db.ref(`coachSessions/${coachSessionID}`).update(coachSession);
}
});
console.log('opentokSessionIdEnd: ', opentokSessionId);
};
My function onCreateCoachSession trigger on a firebase event (it's a cloud function), but it does not end for opentok.createSession to end, i don't understand why as i put an await before.
Can anyone have an idea why my code trigger directly the last console log (opentokSessionIdEnd)
Here is a screenshot on order of console.log :
It's probably a simple problem of async/await that i missed but i cannot see what.
I thanks in advance the community for the help.
You're using createSession in callback mode (you're giving it a callback function), so it doesn't return a Promise, so it can't be awaited.
Two solutions :
1/ Use createSession in Promise mode (if it allows this, see the doc)
let session = null;
try{
session = await opentok.createSession({ mediaMode: 'relayed' })
} catch(err) {
console.log('Error creating session:', error);
}
or 2/ await a Promise
let session;
try {
session = await new Promise((resolve, reject) => {
opentok.createSession({ mediaMode: 'relayed' }, (error, session) => {
if (error) {
return reject(error)
}
resolve(session);
})
})
} catch (err) {
console.log('Error creating session:', err);
throw new Error(err);
}
opentokSessionId = session.sessionId;
console.log('opentokSessionIdBefore: ', opentokSessionId);
// ...
await means it will wait till the promise is resolved. I guess there is no promise returned in this case. you can create your own promise and handle the case

Using loops and promises in transactions in Sequelize

I am currently building a Nodejs, Express, Sequelize (w. PostgreSQL) app, and have run into a few problems with using promises together with transactions and loops.
I am trying to figure out how to use a for loops in a transaction. I am trying to loop through a list of members and create a new user in the database for each of them.
I know the following code is wrong but it shows what I am trying to do.
Can anyone point me in the right direction?
var members = req.body.members;
models.sequelize.transaction(function (t) {
for (var i = 0; i < members.length; i++) {
return models.User.create({'firstname':members[i], 'email':members[i], 'pending':true}, {transaction: t}).then(function(user) {
return user.addInvitations([group], {transaction: t}).then(function(){}).catch(function(err){return next(err);});
})
};
}).then(function (result) {
console.log("YAY");
}).catch(function (err) {
console.log("NO!!!");
return next(err);
});
You should use a Promise.all
var members = req.body.members;
models.sequelize.transaction(function (t) {
var promises = []
for (var i = 0; i < members.length; i++) {
var newPromise = models.User.create({'firstname':members[i], 'email':members[i], 'pending':true}, {transaction: t});
promises.push(newPromise);
};
return Promise.all(promises).then(function(users) {
var userPromises = [];
for (var i = 0; i < users.length; i++) {
userPromises.push(users[i].addInvitations([group], {transaction: t});
}
return Promise.all(userPromises);
});
}).then(function (result) {
console.log("YAY");
}).catch(function (err) {
console.log("NO!!!");
return next(err);
});
I don't believe you need to catch within sequelize transactions as I think it jumps out to the catch on the transaction
Sorry for formatting. On mobile.
Promise.all will wait for all promises to return (or fail) before running the .then, and the .then callback will be all the promise data from each array
You'll need to use the built in looping constructs of bluebird which ships with sequelize:
var members = req.body.members;
models.sequelize.transaction(t =>
Promise.map(members, m => // create all users
models.User.create({firstname: m, email: m, 'pending':true}, {transaction: t})
).map(user => // then for each user add the invitation
user.addInvitations([group], {transaction: t}) // add invitations
)).nodeify(next); // convert to node err-back syntax for express
Depending on your implementation of Node.js this may help. I have the same setup using express, POSTGRES and sequelize.
Personally I'd prefer the async/await (ES6) implementation over then/catch as it is easier to read. Also creating a function that can be called externally improves re-usability.
async function createMemeber(req) {
let members = req.body.members;
for (var i = 0; i < members.length; i++) {
// Must be defined inside loop but outside the try to reset for each new member;
let transaction = models.sequelize.transaction();
try {
// Start transaction block.
let user = await models.User.create({'firstname':members[i], 'email':members[i], 'pending':true}, {transaction});
await user.addInvitations([group], {transaction}));
// if successful commit the record. Else in the catch block rollback the record.
transaction.commit();
// End transaction block.
return user;
} catch (error) {
console.log("An unexpected error occurred creating user record: ", error);
transaction.rollback();
// Throw the error back to the caller and handle it there. i.e. the called express route.
throw error;
}
}
}
if someone is looking for a solution with typescript v4.0.5 using async and await here is what worked out for me. Maybe you can use it on your javascript aplication too but it will depend on the version of it.
const array = ['one','two','three'];
const createdTransaction = sequelize.transaction();
const promises = array.map(async item => {
await model.create({
name: item,
},
{ transaction: createdTransaction },
);
});
Promise.all(promises).then(async values => {
await createdTransaction.commit();
});
First: https://caolan.github.io/async/docs.html
So, easily:
// requiring...
const async = require('async');
// exports...
createAllAsync: (array, transaction) => {
return new Promise((resolve, reject) => {
var results = [];
async.forEachOf(array, (elem, index, callback) => {
results.push(models.Model.create(elem, {transaction}));
callback();
}, err => {
if (err) {
reject(err);
}
else {
resolve(results);
}
});
});
}

Categories

Resources