How to commit transaction after loop over asynchonous updates - node.js

I'm using node-mysql-queues to handle database transactions in my application.
for (lineitem in lineitems) {
transaction.query("SELECT n from inventory WHERE productId = ?", [lineitem], function (err, rows) {
if (err)
transaction.rollback();
var newN = rows[0].n - lineitems[lineitem].quantity;
if (newN >= 0) {
transaction.query("UPDATE inventory SET n = ? WHERE productId = ?", [newN, lineitem], function (err) {
if (err){
transaction.rollback();
console.log(err);
}
//here I want to commit if all updates were successfull!!!
});
}
})
}
As you can see in the code, I don't know how to handle the commit part. If it was synchronous it would be easy, but don't know how ro solve this problem.
Thanks & Regards

This is easy with something like the async module.
async.each(lineitems, performQuery, function(err) {
if(err) {
transaction.rollback();
console.log(err);
return;
}
transaction.commit();
});
function performQuery(lineitem, callback) {
transaction.query("SELECT n from inventory WHERE productId = ?", [lineitem], function (err, rows) {
if (err) return callback(err);
var newN = rows[0].n - lineitems[lineitem].quantity;
if (newN >= 0) {
transaction.query("UPDATE inventory SET n = ? WHERE productId = ?", [newN, lineitem], function (err) {
if (err) return callback(err);
callback();
});
}
});
}

I found a solution for my problem. Since I had problems with doing a select and then an update depending on the result of the select, I implemented something like a conditional update.
But see my code:
mysql.getTransaction(function (err, transaction) {
//For each item in the cart, call the performUpdate method
//If an error occures, rollback the whole transaction
async.each(lineitems, performUpdate, function (err) {
if (err) {
transaction.rollback();
res.json(err.message);
return;
}
//Since we are going to call another callback, we need to pause the transaction, else it would be committed automatically
transaction.pause();
//If the Updates were successfull, create an Order in MongoDB
orderController.createMongoOrder(lineitems, req.session.cart.total, req.session.passport.user, function (err) {
if (err) {
//If there is a Problem with Mongo, cancel the transaction
transaction.resume();
transaction.rollback();
res.json(err.message);
} else {
//Else commit the transaction and empty the cart
transaction.resume();
transaction.commit();
req.session.cart = {
products: {},
count: 0,
total: 0
};
res.json("Order accepted!");
}
})
});
function performUpdate(lineitem, callback) {
//This query can be seen as conditional update. If the number of articles in stock is not sufficient, there will be no affectedRows in the returned info message
transaction.query("UPDATE inventory SET n = n -? WHERE productId = ? AND n >= ?", [lineitem.quantity, lineitem.id, lineitem.quantity],function (err, info) {
if (err) {
return callback(err);
} else {
//if for any item there is no affectedRow, this means the Updated failed. This should make the whole transaction roll back so we return an error to the callback
if (info.affectedRows != 1) {
return callback(new Error("Article: " + lineitem.productObject.name + " out of stock!"))
}
return callback(null, info);
}
}).execute()
}
})

Related

Transaction over two Tables with Postgres in Sails.js

I am trying to implement a transaction in Node.js (Sails.js) with Postgresql and Sails Waterline.js ORM.
I tried to adapt this answer. In that answer we have only one table. In my problem I have 2 tables which have to be locked till the transaction is over.
What should this code do:
find User with :Id
if this User.coins > 0:
Update User.coins = 0
Create a new record in the table MyTransaction
But the snippet doesn't lock the transaction over both tables:
try {
// Start the transaction
sails.models.user.query("BEGIN", function (err) {
if (err) {
throw new Error(err);
}
// Find the user
sails.models.user.findOne(currentUser.id).exec(function (err, user) {
if (err) {
throw new Error(err);
}
if (user.coins > 0) {
var params = {
user_id: req.session.passport.user,
publicAddress: bitcoin_address,
amount: user.coins,
wohnAdresse: wohnAdresse
}
// Update the user balance
user.coins = 0;
// Save the user
user.save(function (err) {
if (err) {
throw new Error(err);
}
sails.models.myTransaction.create(params).exec(function (err, transaction) {
if (err) {
payout = {success: false};
payout.transactionError = err;
console.log("ROLLBACK! ROLLBACK!")
return res.serverError(e);
}
// Commit the transaction
sails.models.user.query("COMMIT", function (err) {
if (err) {
throw new Error(err);
}
payout = {success: true};
console.log("PAYOUT PAYOUT", payout);
console.log("PAYOUT PAYOUT", transaction);
return res.json(payout);
});
});
});
} // END of if user.coins>0
});
});
}
// If there are any problems, roll back the transaction
catch (e) {
User.query("ROLLBACK", function (err) {
// The rollback failed--Catastrophic error!
if (err) {
return res.serverError(err);
}
// Return the error that resulted in the rollback
return res.serverError(e);
});
}
So when I run a loop(5times) in the frontend console, then it creates 5 records in the myTransaction table. How Can I do this properly?

Resolve not working in loop Node.js

Hi I have a problem running a loop and getting the return data using Promises.
I have a getStudentMarks method for getting students marks from the database in subject wise.
getStudentMarks: function(studentId, studentStandard) {
console.log("getStudentMarks invoked...");
return new Promise(function(resolve, reject) {
r.table('student_subjects').filter({
"studentId": studentId,
"studentStandard": studentStandard
}).pluck("subjectId", "subjectName").run(connection, function(err, cursor) {
if (err) {
throw err;
reject(err);
} else {
cursor.toArray(function(err, result) {
if (err) {
throw err
} else {
console.log(result.length);
if (result.length > 0) {
studentSubjectArray = result;
var studentMarksSubjectWiseArray = [];
studentSubjectArray.forEach(function(elementPhoto) {
r.table('student_marks').filter({
"studentId": studentId,
"subjectId": studentSubjectArray.subjectId
}).run(connection, function(err, cursor) {
if (err) {
throw err;
reject(err);
} else {
cursor.toArray(function(err, result_marks) {
var studnetMarksDataObject = {
subjectId: studentSubjectArray.subjectId,
subjectName: studentSubjectArray.subjectName,
marks: result.marks
};
studentMarksSubjectWiseArray.push(studnetMarksDataObject);
});
}
});
});
resolve(studentMarksSubjectWiseArray);
}
}
});
}
});
});
}
I'm invoking the method by,
app.post('/getStudentMarks', function(req, reqs) {
ubm.getStudentMarks(req.body.studentId, req.body.studentStandard)
.then((data) => {
console.log('return data: ' + data);
})
.catch((err) => {
console.log(err);
});
});
When I run the code its working absolutely fine there is no error. I get all the student marks object in the studentMarksSubjectWiseArray array. But the problem is even before the studentSubjectArray loops gets completed, the resolve is getting executed and I'm getting a blank array as return. How do I solve the problem. I understand that I'm not doing the Promises right. I'm new to Promises so I'm not being able to figure out the right way.
That happens because inside your studentSubjectArray.forEach statement you perform set of asynchronous operations r.table(...).filter(...).run() and you push their result into the array. However, those actions finish after you perform the resolve(), so the studentMarksSubjectWiseArray is still empty. In this case you would have to use Promise.all() method.
let promisesArray = [];
studentSubjectArray.forEach((elementPhoto) => {
let singlePromise = new Promise((resolve, reject) => {
// here perform asynchronous operation and do the resolve with single result like r.table(...).filter(...).run()
// in the end you would perform resolve(studentMarksDataObject)
r.table('student_marks').filter({
"studentId": studentId,
"subjectId": studentSubjectArray.subjectId
}).run(connection, function(err, cursor) {
if (err) {
throw err;
reject(err);
} else {
cursor.toArray(function(err, result_marks) {
var studnetMarksDataObject = {
subjectId: studentSubjectArray.subjectId,
subjectName: studentSubjectArray.subjectName,
marks: result.marks
};
resolve(studnetMarksDataObject);
});
}
});
});
promisesArray.push(singlePromise)
});
Promise.all(promisesArray).then((result) => {
// here the result would be an array of results from previously performed set of asynchronous operations
});

Mongoose: Documents aren't updating

I'm finding documents by _id in a loop and updating a boolean in each document:
db.items.findById(key, function(error, item) {
item.flags.cake = false;
item.update(function(error, zzz) {
if(error) return next(error);
console.log('output ',zzz);
});
});
But the documents will not update. The mongoose schema for item:
flags: {
cake:Boolean
}
Use the save() method instead which makes use of a callback that will receive three parameters you can use:
1) err if an error occurred
2) item which is the saved item
3) numAffected will be 1 when the document was successfully persisted to MongoDB, otherwise 0.
Items.findById(key, function(error, item) {
item.flags.cake = false;
item.save(function (err, item, numAffected) {
if (err) console.log(err)
console.log('output ', item);
});
});
As an extra measure of flow control, save will return a Promise.
item.save().then(function(item) {
console.log('output ', item);
});

How to get return value from the function while initialising the object in node js

I am mew to node js, I have something like this,
get_contacts(data, function(contacts) {
if (contacts.length) {
var count = contacts.length;
for (var i = 0; i < count; i++) {
result = {
id: contacts[i].id,
name: contacts[i].name,
sent1: get_sent(data.userId, contacts[i].id, function(resp) {
result.sent = resp.count;
}),
}
result1[i] = result;
}
output = {
contacts: result1,
}
} else {
output = {
error: "No Contacts.",
}
}
res.writeHead(200, {'content-type': 'text/html'});
res.end(JSON.stringify(output));
});
get_contacts is a callback function which will return contact list.result1 & result are objects. Now value for sent should come from a function get_sent, and get sent is like this
function get_sent(userId, contactId, callback) {
pool.getConnection(function(err, connection) {
connection.query("my query here", function(err, rows) {
connection.release();
if (!err) {
callback(rows);
} else {
console.log(err)
}
});
});
}
But im not getting any value since nodejs. since nodejs is async it is not waiting for the function to return value. I know, im doing it in wrong way. Please help
You need to use a callback. In simple words is a function that you'll execute after something happens. You should read more about that. You should get a book about javascript but you can start reading here for example.
About your case, you could solve it like this
//Asumming that you object `result` is global.
result = {
id: contacts[i].id,
name: contacts[i].name,
sent: -1 //Some default value
}
//Just to put the code into a function, you have to put it where you need
function constructObject (){
get_sent(uId, cId, function(err, total){
if(err){
console.log("Something was wrong.", err);
}
result.sent = total;
//Here you have your object completed
console.log(result);
});
}
//You need to use a callback
function get_sent(uId, cId, callback) {
pool.getConnection(function(err, connection) {
//Note that I add an alias here
connection.query("SELECT count(*) as total FROM table_name", function(err, rows) {
connection.release();
if (!err) {
//I am returning the result of the query and a null error
callback(err, rows[0].total);
} else {
console.log(err);
//I am returning an error
callback(err);
}
});
});
}
//For example you could call this function here
constructObject();
And it depends of what are you doing exactly but Maybe you need a callback on your constructObject too.

How to create callback post saving an array of objects?

Assuming I have the following in a function:
exports.addnames = function(req, res) {
var names = ["Kelley", "Amy", "Mark"];
for(var i = 0; i < names.length; i++) {
(function (name_now) {
Person.findOne({ name: name_now},
function(err, doc) {
if(!err && !doc) {
var personDoc = new PersonDoc();
personDoc.name = name_now;
console.log(personDoc.name);
personDoc.save(function(err) {});
} else if(!err) {
console.log("Person is in the system");
} else {
console.log("ERROR: " + err);
}
}
);
)(names[i]);
}
My issue is after I save the names, I want to return the results:
Person.find({}, function(err, doc) {
res.json(200, doc);
})
Though I have a callback for names, it appears that the last block of code (Persons.find({})) gets executed before the calls to save all the names is complete... thusly when the user goes to the url in the browser, "doc" is empty... Is there some way I can ensure that the Persons.find({}) is called after the for loop completes?
The easiest way to do things like this is to use an async library like the aptly named async which can be found at https://github.com/caolan/async.
If you have a list of names that you want to save and then return when complete, it would look like:
// save each of the names asynchronously
async.forEach(names, function(name, done) {
Person.findOne({name: name},
function(err, doc) {
// return immediately if there was an error
if(err) return done(err);
// save the person if it doesn't already exist
if(!doc) {
var personDoc = new PersonDoc();
personDoc.name = name;
console.log(personDoc.name);
// the async call is complete after the save completes
return personDoc.save(done);
}
// or if the name is already there, just return successfully
console.log("Person is in the system");
done();
}
);
},
// this function is called after all of the names have been saved
// or as soon as an error occurs
function(err) {
if(err) return console.log('ERROR: ' + err);
Person.find({}, function(err, doc) {
res.json(200, doc);
})
});

Resources