Too Many Promises in single nodejs route - node.js

I want to know if one of the promises fail how can i rollback or cancel already happened operations.
and 2nd is there any other way to optimize code, it takes more time to resolve.
As the number of joined player will increase it will be taking more time is there any way to optimise it more
route.put("/UpdateResult/:id", Get_User_id, async (req, res) => {
try {
const response = await tournamentschema.findByIdAndUpdate(
req.params.id,
{
Game_Name: req.body.Game_Name,
Total_Players: req.body.Total_Players,
Prize_Pool: req.body.Prize_Pool,
Joined_User: req.body.Joined_User,
Is_Finished: true,
},
{ new: true, runValidators: true }
);
response.Joined_User.forEach(async (Player) => {
await UserModal.findByIdAndUpdate(
Player.UserId,
{
$inc: {
Wallet_Coins: Player.Kills * parseInt(response.Prize_Pool),
},
},
{ new: true }
);
});
return res.send("Result Updated Sucessfully");
} catch (error) {
console.log(error.message);
res.status(500).send(error.message);
}
});

for optimize :
in ES7 and 8 we are have new feature called promise all in for your problem its better you don't use forEach for your await function its better in first you get all your id in new array like this :
let playerCacheId = []
response.Joined_User.forEach((Player) => {
playerCacheId.push(player.id)
}
await Promise.all(playerCacheId .map(playerId => UserModal.findByIdAndUpdate(
playerId ,
{
$inc: {
Wallet_Coins: Player.Kills * parseInt(response.Prize_Pool),
},
},
{ new: true }
);
});
))

To rollback Operations use MongoDB Transactions

Related

node mongoose how to auto increment

Trying to follow the example here:
https://www.tutorialspoint.com/mongodb/mongodb_autoincrement_sequence.htm
export interface RowProps {
id?: number; // This is to auto increment
todoText: string;
}
const addAutoIncrement = async ({ db, collectionName, todoText }) => {
const getNextSequenceValue = (sequenceName: string) => {
const sequenceDocument = db
.collection<RowProps>(collectionName)
.findAndModify({
query: { _id: sequenceName },
update: { $inc: { sequence_value: 1 } },
new: true,
});
console.log('sequenceD', sequenceDocument)
return sequenceDocument.sequence_value;
};
db.collection<RowPropsClient>(collectionName).insertOne(
{
id: getNextSequenceValue('id'),
todoText
},
(err) => {
if (err) {
console.log("err");
}
}
);
}
// db is already defined and works
// I can add to the collection so this also works.
addAutoIncrement({ db, collectionName: 'todos', todoText: 'hello' });
Error: throw new Error('Collection#findAndModify unimplemented by driver');
^
Error: Collection#findAndModify unimplemented by driver
update
Tried to follow this example:
https://medium.com/#salonimalhotra1ind/how-to-increment-a-number-value-in-mongoose-785066ba09d8
const addAutoIncrement = async ({ db, collectionName, todoText }) => {
const modelTodo = db.model(collectionName, TodosSchema);
const res = await new modelTodo({ todoText }).save();
const { _id } = res;
return new Promise((resolve, reject) => {
modelTodo.findOneAndUpdate(
{ _id },
{ $inc: { id: 1 } },
{ new: true },
(err, res) => {
if (err) {
reject(err);
}
resolve(res);
}
);
});
};
**The result is just setting the value to 1 each time - not incrementing**
Collection#findAndModify() is a method that is implemented in the MongoDB shell, but not in the Node.js driver.
You should use Collection#findOneAndUpdate instead:
const { value : sequenceDocument } = db
.collection<RowProps>(collectionName)
.findOneAndUpdate({
{ _id: sequenceName },
{ $inc: { sequence_value: 1 } },
{ returnDocument : 'after' } // equivalent to `new: true`
});
ok I don't know why I didnt do this before. All the online examples make everything unnecessarily complicated.
Just get the total count and then add it.
const addAndIncrement = async ({ db, collection, todoText }) => {
const connectedModel = db.model(collection, TodosSchema);
const documentCount = await connectedModel.count({}); // get the total count
return new connectedModel({ todoText, id: documentCount }).save();
};
Unless anyone comes up with a more performant way, this is what I'm going with.

Delay in return value - nodejs & mongoose

I'm fairly new to nodejs and I'm doing a full stack developer challenge from devchallenges.io (Shoppingify). Below, I'm trying to increase the quantity value based on whether the user clicks to increase or decrease the item quantity. However, there's a slight delay between the return value from the request and the actual value in the database. It seems that the value updates immediately which is great however, the return value in the request is the previous value rather than being the current quantity value in the database.
mongoDB Database
// #route PUT api/list/item/quantity/:id
// #desc update item quantity
// #access Private
router.put('/item/quantity/:id', auth, async (req, res) => {
const { action } = req.body;
try {
let list = await List.findOne({ user: req.user.id });
// find current quantity
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
// increase quantity
if (action === 'increase') {
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity + 1 } },
{ new: true }
);
} else {
// decrease quantity
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
}
res.json(item.quantity);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
You are defining item in here:
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
At this point list is the "old" version of the object, you want to be doing the same after the update when the list object is updated and only then to return it.
// this is the original "list" item
let item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
...
update list
...
// now "list" is updated
item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
I will just add two additional tips to improve performance, they are mutually exclusive so you'll have to choose one of the two.
in the update query add the list._id to it, If I were to guess the collection does not have an index on the items field ( and if it does it's a bad idea usually ). this means when you updated just using the item._id field it takes longer for mongo to find the object. it's quick change to both updates:
list = await List.findOneAndUpdate(
{ _id: list._id, 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
(my preferred option) do it in a single call, using the update arrayFilters option, like so:
const list = await List.findOneAndUpdate(
{
user: req.user.id,
},
{
$inc: {
'items.$[elem].quantity': action === 'increase' ? 1 : -1,
},
},
{
arrayFilters: [
{
'elem._id': new ObjectId(req.params.id),
},
],
new: true,
});
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
Mongo Playground
In my opinion now your route looks much better, you're also cutting down from 2 db calls to 1.

ReferenceError: Cannot access 'savedPost' before initialization

My POST method which throws the ReferenceError:
postsRouter.post('/:postNumber', async (req, res, next) => {
const postDate = new Date().toUTCString()
const post = new Post({
...req.body,
poster: req.body.poster
? (req.body.poster.includes(config.AP) ? req.body.poster.replace(config.AP, '') : req.body.poster)
: 'nonamer',
date: postDate,
IP: req.socket.remoteAddress,
admin: req.body.poster.includes(config.AP) ? true : false
})
try {
const [savedPost, updatedBubble] = await Promise.all([
/* save() doesn't save the post with the new postnumber which it should (see the pre('save')
function in the next code block), it's only in the post that is being returned while saving. */
post.save().catch(e => { console.log('Task 1 failed!'); next(e) }),
// req.params.postNumber is the postnumber of the bubble a.k.a thread being replied to.
Post.findOneAndUpdate(
{ postNumber: req.params.postNumber },
{ $push: { replies: savedPost.postNumber } })
.catch(e => { console.log('Task 2 failed!'); next(e) })
])
res.status(201).json(savedPost)
console.log('!!!!! ' + req.socket.remoteAddress + ' saved a post: ' + JSON.stringify(savedPost))
} catch (e) {
next(e)
}
})
Also this mess of a code is in my Post model (which utilizes Counter model):
// Increase postnumber in counter collection's sole document, and insert it into new post.
postSchema.pre('save', function(next) {
let post = this
const ccc = Counter.findByIdAndUpdate(
{ '_id': 'postNumCounter' },
{ '$inc': { 'currentPostNum': 1 }}, // Works
{ new: true },
// inserting postnumber not working, fix
function(error, counter) {
post.postNumber = counter.currentPostNum
}
)
// Not working either
post.postNumber = ccc.currentPostNum
next()
})
My goal is I simply want to grab the currentPostNum after incrementing it, and insert that into the post before saving it.
Solved myself. I made the pre('save') function asynchronous as well, and ditched Promise.all in the controller POST method. Now everything works in tandem.
postSchema.pre('save', async function (next) {
let post = this
const counter = await Counter.findByIdAndUpdate(
{ '_id': 'postNumCounter' },
{ '$inc': { 'currentPostNum': 1 } }, // Works
{ new: true }
)
post.postNumber = counter.currentPostNum
next()
})

Is there any way that I can use .find() method to JSON return file?

I am trying to do a query from users' location + users' pick on the category list. Since the $geoNear is not accepting two parameters. I've been trying to do a query from the return value. However, I am getting an error saying that I can't do .find() to a JSON format file (not a function).
static async getuserinitialplace(favCatgories, userCoordinates) {
console.log(userCoordinates);
try {
const places = await Place.aggregate([
{
$geoNear: {
near: { type: 'Point', coordinates: userCoordinates },
distanceField: 'calcDistance',
maxDistance: 25000,
spherical: true,
},
},
{ $limit: 200 },
]);
return places;
} catch (err) {
return console.log(err);
}
}
I'd also tried to put it inside of the function, then I get a callback error + others.
static async getuserinitialplacea(req, res) {
const result = await PlaceService.getuserinitialplacea(
JSON.parse(req.query.userCoordinates),
).find({category: req.query.favCatgories})
res.send(result)
}
Is there anyway that I can query with two parameters when I use $geoNear?
Try to write both args for a getuserinitialplace in a variable and then use it them as args, like this:
static async getuserinitialplacea(req, res) {
try {
let arg = JSON.parse(req.query.userCoordinates);
const result = await PlaceService.getuserinitialplace(arg)
res.send(result)
} catch (e) {
console.error(e)
}
}
Also this .find({category: req.query.favCatgories}) find part doesn't make sence. Aggregation doesn't return you a Mongoose Document or a Mongoose Model.
If your should be used as a second argument if your function, then use it, like this:
let arg = JSON.parse(req.query.userCoordinates);
let another_arg = req.query.favCatgories;
const result = await PlaceService.getuserinitialplace(arg, another_arg )
or even better:
/** Code */
const { userCoordinates, favCatgories} = req.query;
const result = await PlaceService.getuserinitialplace(favCatgories, userCoordinates)
/** Other code */
And if your want to find something after $geoNear stage, just add another stage with $match and query it. Like this:
static async getuserinitialplace(favCatgories, userCoordinates) {
console.log(userCoordinates);
try {
const places = await Place.aggregate([
{
$geoNear: {
near: { type: 'Point', coordinates: userCoordinates },
distanceField: 'calcDistance',
maxDistance: 25000,
spherical: true,
},
},
/** You could also add it before $geoNear stage */
{ $match: { nameOfField: favCatgories } },
/** OR { $match: favCatgories }, */
/** BUT favCatgories should be an object */
{ $limit: 200 },
]);
return places;
} catch (err) {
return console.log(err);
}
}

When I make db process inside async map function, I can't avoid duplicate

I want to add pallet barcode to palletBarcodes field of record.But there is check for avoid add same palletBarcode.I am using below function. But check is not working inside async map function.
myService.js
const palletBarcodes = ["TP2","TP2"]
await Promise.all(palletBarcodes.map(async (palletBarcode) => {
const promise = await this.addPalletBarcode({ transferId, barcode: palletBarcode });
return promise;
}));
async addPalletBarcode({ transferId, barcode, pickerId }) {
const { TransferDataAccess } = this;
const transfer = await TransferDataAccess.getTransfer({ transferId });
if (!transfer) {
throw new TransferNotFoundError();
}
if (transfer.palletBarcodes.length && transfer.palletBarcodes.includes(barcode)) {
throw new PalletBarcodeAlreadyExistsError({ barcode });
}
return TransferDataAccess.pushPalletBarcode({ transferId, barcode });
}
transferDataAccess:
async pushPalletBarcode({ transferId, barcode }) {
const { TransferModel } = this;
return TransferModel
.findOneAndUpdate({
_id: transferId,
},
{
$push: {
palletBarcodes: barcode,
},
})
.lean()
.exec();
}
Instead of $push use $addToSet. $addToSet will treat your key in document as a set and that will automatically avoid duplicates.
You query would then become -
TransferModel.findOneAndUpdate(
{ _id: transferId },
{ $addToSet: { palletBarcodes: barcode } }
);

Resources