Reputation: 41
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
Upvotes: 0
Views: 1188
Reputation: 2453
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.
Upvotes: 2