router.put('/', async (req, res) => {
try {
const today = new Date();
var month = (today.getMonth()) + 1;
var year = today.getFullYear();
var field = year + "-" + month;
var category = req.body.category;
var date = today.getDate();
const session = mongoose.startSession();
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' }
};
const news = await News.findById(req.body.id);
var newsDate = new Date(news.createdAt).getDate();
var newsMonth = new Date(news.createdAt).getMonth() + 1;
var newsYear = new Date(news.createdAt).getFullYear();
// (await session).startTransaction();
// (await Publisher.findOneAndUpdate({ registrationNumber: req.body.registrationNumber }, { $inc: { [`monthlyViews.${field}`]: 1, [`categoriesViews.${category}`]: 1 } })).$session(session);
// if (newsDate == date && newsMonth == month && newsYear == year) {
// (await News.findOneAndUpdate({ _id: req.body.id }, { $inc: { [`views.${field}`]: 1, [`totalViews`]: 1, ['publishedDateViews']: 1 } })).$session(session);
// } else {
// (await News.findOneAndUpdate({ _id: req.body.id }, { $inc: { [`views.${field}`]: 1, [`totalViews`]: 1 } })).$session(session);
// }
// (await session).commitTransaction();
// res.status(200).send({ status: 1 });
const transactionResult = (await session).withTransaction(async () => {
var newsResult;
if (newsDate == date && newsMonth == month && newsYear == year) {
newsResult = (await News.findOneAndUpdate({ _id: req.body.id }, { $inc: { [`views.${field}`]: 1, [`totalViews`]: 1, ['publishedDateViews']: 1 } })).$session(session);
} else {
newsResult = (await News.findOneAndUpdate({ _id: req.body.id }, { $inc: { [`views.${field}`]: 1, [`totalViews`]: 1 } })).$session(session);
}
if (!newsResult) {
return (await session).abortTransaction();
}
const pubresult = (await Publisher.findOneAndUpdate({ registrationNumber: req.body.registrationNumber }, { $inc: { [`monthlyViews.${field}`]: 1, [`categoriesViews.${category}`]: 1 } })).$session(session);
if (!pubresult) {
return (await session).abortTransaction();
}
}, transactionOptions).then(result => { res.status(200).send({ status: 1 }); }).catch(e => {
console.log(e);
res.status(400).send({ status: 0 });
});
} catch (error) {
// (await session).abortTransaction();
console.error(error);
res.status(500).send({ status: 0, message: "Internal Server error" });
} finally {
(await session).endSession();
}
});
Whenever one of the transaction fails then also it did not aborts the transaction and transaction got partially committed. for example: when I send wrong registration number using postman then Publisher.findoneandUpdate an returns "TypeError: Cannot read property '$session' of null" and the transaction should be aborted but it got partially committed like the code above Publisher line save it in the document. i am using mongodb atlas
My understanding of your problem is that when an error is thrown inside your transaction, the transaction isn't aborted?
When reviewing your code you seem to mix async/await with promises which leads to it being less readable and also things work slightly differently.
When using async/await, any return
statement will cause the promise to resolve and not reject, whereas when using a throw
statement, the promise will be rejected instead of resolved.
Now, you catch any error that is thrown inside your callback using .catch
, however, if the method throws because $session
cannot exist on null
then your (await session).abortTransaction()
isn't called.
You use try/catch/finally, however, both catch
and finally
aren't executed as no error is thrown. You've caught any error inside your callback already.
Instead, use async/await purely and see if makes any difference (excuse the auto-formatted code):
router.put('/', async (req, res) => {
try {
const today = new Date();
var month = today.getMonth() + 1;
var year = today.getFullYear();
var field = year + '-' + month;
var category = req.body.category;
var date = today.getDate();
const session = await mongoose.startSession(); // await here
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' },
};
const news = await News.findById(req.body.id);
var newsDate = new Date(news.createdAt).getDate();
var newsMonth = new Date(news.createdAt).getMonth() + 1;
var newsYear = new Date(news.createdAt).getFullYear();
// (await session).startTransaction();
// (await Publisher.findOneAndUpdate({ registrationNumber: req.body.registrationNumber }, { $inc: { [`monthlyViews.${field}`]: 1, [`categoriesViews.${category}`]: 1 } })).$session(session);
// if (newsDate == date && newsMonth == month && newsYear == year) {
// (await News.findOneAndUpdate({ _id: req.body.id }, { $inc: { [`views.${field}`]: 1, [`totalViews`]: 1, ['publishedDateViews']: 1 } })).$session(session);
// } else {
// (await News.findOneAndUpdate({ _id: req.body.id }, { $inc: { [`views.${field}`]: 1, [`totalViews`]: 1 } })).$session(session);
// }
// (await session).commitTransaction();
// res.status(200).send({ status: 1 });
await session.withTransaction(async () => {
var newsResult;
// if you haven't already, learn about strict equality
if (newsDate == date && newsMonth == month && newsYear == year) {
newsResult = (
await News.findOneAndUpdate(
{ _id: req.body.id },
{
$inc: {
[`views.${field}`]: 1,
[`totalViews`]: 1,
['publishedDateViews']: 1,
},
}
)
).$session(session);
} else {
newsResult = (
await News.findOneAndUpdate(
{ _id: req.body.id },
{ $inc: { [`views.${field}`]: 1, [`totalViews`]: 1 } }
)
).$session(session);
}
if (!newsResult) {
// if not found, catch {} will catch it
throw new Error('news result does not exist');
}
const pubresult = (
await Publisher.findOneAndUpdate(
{ registrationNumber: req.body.registrationNumber },
{
$inc: {
[`monthlyViews.${field}`]: 1,
[`categoriesViews.${category}`]: 1,
},
}
)
).$session(session);
if (!pubresult) {
throw new Error('publisher does not exist');
}
}, transactionOptions);
res.status(200).send({ status: 1 });
} catch (error) {
session.abortTransaction(); // await if async
if (
error.message === 'publisher does not exist' ||
error.message === 'news result does not exist'
) {
res.status(404).send({ status: 0 }); // not found
return;
}
// handle validation errors or whatever you used 400 for
res.status(500).send({ status: 0, message: 'Internal Server error' });
} finally {
session.endSession();
}
});
Please note: I haven't tested this, however, try it and see if your issue has been corrected. If abortTransaction
and endSession
are asynchronous then use await as required.