I'm coming from MS SQL so to make things easier in my mind, I'm trying to create the equivalent of a sequential primary key. Using some online articles and API references I've constructed the following:
function getNextSequence(name) {
var ret = db.collection('counters').findOneAndUpdate(
{_id: name },
{ $inc: { seq: 1 } },
{returnNewDocument: true}
)
return ret.seq;
}
console.log(getNextSequence("sms_id"))
db.collection('SMS').insertOne({
"_id":getNextSequence("sms_id"),
record
}, (err, result) => {
if (err) {
return console.log('Unable to insert record', err);
}
console.log(JSON.stringify(result.ops, undefined, 2));
});
The problem is the getNextSequence() function is continuing before the findOneAndUpdate() method inside of it finishes. After some debugging, I've determined that it is a promise that is pending, so I tried making the following changes:
function getNextSequence(name) {
var ret = db.collection('counters').findOneAndUpdate(
{_id: name },
{ $inc: { seq: 1 } },
{returnNewDocument: true}
).then(() => {
return ret.seq
});
}
But it still continues on. How can I get it to wait for the promise to finish?
You want to construct a sequence of async executions, which is simple with Promises by returning them throughout your code:
function getNextSequence(name) {
return db.collection('counters').findOneAndUpdate(
{_id: name },
{ $inc: { seq: 1 } },
{returnNewDocument: true}
).then(ret => ret.seq);
}
And then using the function:
getNextSequence('sms_id').then((seq) => {
return db.collection('SMS').insertOne({
"_id": seq,
record
});
}).then((result) => {
console.log(JSON.stringify(result.ops, undefined, 2));
}).catch((err) => {
console.log('Unable to insert record', err);
});
Note that the error passed to the .catch callback can either be from getNextSequence or the insertOne method call on the SMS collection.
If you return another promise from within the callback of a Promise's .then-call, the next .then-call will wait for that promise to fulfill. See the below snippet for an example:
function waitAndLog (msg, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(msg)
resolve()
}, ms)
})
}
waitAndLog("there", 1000).then(() => {
return waitAndLog("is", 1000)
}).then(() => {
return waitAndLog("no", 1000)
}).then(() => {
return waitAndLog("spoon", 1000)
}).then(() => {
console.log("Sequence complete")
})
Related
I was trying to fetch data from neo4j database.
Here is my function for getting data from database which I found on their official website and have modified it little bit:
function receiveDataFromDB() {
var neo4j = require("neo4j-driver");
var driver = neo4j.driver(
"neo4j://localhost",
neo4j.auth.basic("neo4j", "something")
);
console.log(driver);
var session = driver.session({
database: "neo4j",
defaultAccessMode: neo4j.session.READ,
});
session.run(`match (n) return n`).subscribe({
onKeys: (keys) => {
console.log(keys);
},
onNext: (record) => {
console.log(record.get("n"));
},
onCompleted: () => {
session.close(); // returns a Promise
},
onError: (error) => {
console.log(error);
},
});
}
So this function only console.log-s it:
but I want it to use outside the function. I've tried returning return record.get("n") inside onNext but got errors instead.
You can simply use the try-catch equivalent of your query, like this:
try {
const result = session.run(`match (n) return n`);
} catch (error) {}
finally {
session.close();
}
Or try setting your result in a variable, like this:
const result = [];
session.run(`match (n) return n`).subscribe({
onKeys: (keys) => {
console.log(keys);
},
onNext: (record) => {
result.push(record.get("n"));
},
onCompleted: () => {
session.close(); // returns a Promise
},
onError: (error) => {
console.log(error);
},
});
I'm new to javascript and i'm having a hard time making my response return to wait for my mongodb query finish running inside a forEach loop.
My code is currrently:
exports.applyThesaurus = (req, res, next) => {
let numModified = 0;
var prom = new Promise((resolve,reject) => {
req.file.forEach((obj,idx) => {
wos.updateMany(
{ creator: req.userData.userId},
{ $set: { [req.body.field+".$[element]"] : obj.replaceWith } },
{ arrayFilters: [ { "element": { $in: obj.replaced } } ] }
).then((result) => {
console.log(result.nModified)
numModified += result.nModified
})
.catch((err) => {
res.status(500).json('There was an error while applying the thesaurus.');
})
if( idx === req.file.length -1) {
resolve()
}
})
})
prom.then(() => {
console.log('im returning');
res.status(200).json({message: numModified + ' itens replaced successfully'});
})
}
What happens is that the "i'm returning" console log triggers before the one logging result.nModified
I need to be able to run all the updateMany queries and then respond with the number of updated itens.
Any tips? Thank you very much!
your code is trying to return resolve before your updateMany executes.
if (idx === req.file.length - 1) {
resolve() // this resolve get's executed befour updateMany get executed
}
this might give you a better understanding of the callbacks and why it is happening. as said if you want to resolve the promise after your updateMany finishes execution you need to update your code as below:
exports.applyThesaurus = (req, res, next) => {
let numModified = 0;
var prom = new Promise((resolve, reject) => {
let updateManyPromisesArray = []
req.file.forEach((obj, idx) => {
updateManyPromisesArray.push(wos.updateMany({
creator: req.userData.userId
}, {
$set: {
[req.body.field + ".$[element]"]: obj.replaceWith
}
}, {
arrayFilters: [{
"element": {
$in: obj.replaced
}
}]
}))
Promise.all(updateManyPromisesArray)
.then((result) => {
if (idx === req.file.length - 1) {
resolve()
}
})
.catch((err) => {
res.status(500).json('There was an error while applying the thesaurus.');
})
})
})
prom.then(() => {
console.log('im returning');
res.status(200).json({
message: numModified + ' itens replaced successfully'
});
})
}
Also, you should start using async and await for avoiding these kinds of callback hells situations.
I apologize in advance because I have seen other questions on this very topic, but I do not understand what I am doing wrong and how to fix it. Please have a look at my code:
function getSum(productID) {
Rating.aggregate(
[
{
$group: {
_id: "$productID",
total: {
$sum: "$rating"
}
}
}
],
function (err, result) {
if (err) {
res.send(err);
} else {
//console.log("product-sum: " + req.body.productID)
const count = result.find(item => item._id === productID.productID);
console.log("getSum count: ", count.total);
return count.total;
}
}
);
}
router.route('/compute-rating').post((req, res) => {
console.log("compute Rating: ", req.body.data);
var productID = req.body.data;
var sum = getSum(productID).then( //getting the error here
res.json({ sum })
);
});
getSum() returns a valid number from count.total.
Once I get the sum, I plan to chain another .then onto the existing then and call another function with the productID, but I need to use the sum later in the computer-rating route.
In the code, I have a comment that shows where the error, "Cannot Read Property 'then' of Undefined", is occurring. Why am I getting this error and how can I fix it?
Thanks.
Edit:
I wanted to show my final solution so others could benefit from my experience. Hopefully I did not create any major Javascript violations in my code. I ended up using Promise.all because I had to perform a calculation based on 2 returned values.
function getSum(productID) {
return new Promise(async (resolve, reject) => {
const result = await Rating.aggregate( //sum of a column in MongoDB
[
{
$group: {
_id: "$productID",
total: {
$sum: "$rating"
}
}
}
]
);
try {
var sum = result.find(item => item._id === productID.productID);
if (sum !== undefined) {
resolve(sum);
console.log("getSum sum: ", sum);
}
else {
reject("invalid product id");
}
}
catch (e) {
reject(e);
}
});
}
function getCount(productID) {
return new Promise(async (resolve, reject) => {
var result = await Rating.countDocuments(productID)
.then(count => {
console.log("getCount count:", result);
var documentCount = { count: count }
resolve(documentCount);
})
.catch(err => reject(err));
});
}
router.route('/compute-rating').post((req, res) => {
console.log("compute Rating: ", req.body.data);
var productID = req.body.data;
Promise.all([getSum(productID), getCount(productID)])
.then(results => {
console.log("compute rating results: ", results);
if (results[1].count > 0) {
res.status(200).json({ rating: results[0].total / results[1].count });
}
else {
res.status(200).json({ rating: 0 });
}
})
.catch(err => {
res.status(400).json({ error: err });
})
});
Your getSum doesn't return anything. You are returning only from the callback function, not the getSum function.
You should make it async.
And also you are doing res.send in that function, while not having access to res object.
For example you can do it like this:
async function getSum(productID) {
const result = await Rating.aggregate(
[
{
$group: {
_id: "$productID",
total: {
$sum: "$rating"
}
}
}
]
);
const count = result.find(item => item._id === productID.productID);
console.log("getSum count: ", count.total);
return count.total;
}
router.route('/compute-rating').post((req, res) => {
console.log("compute Rating: ", req.body.data);
var productID = req.body.data;
// EDIT: This should work
getSum(productID).then(sum => {
res.json({ sum })
});
});
then only works on functions that return a promise. your function getSum is not returning anything, if Rating.aggregate function returns a promise, aka accepts a then, then you should return this aggregate, simply add return before calling it.
Now if aggregate doesn't return a promise, and I'm guessing so because you're passing a callback function to it, you might want to return a promise that resolves using promise.resolve in this callback body.
I have an array of object and I want an atomic transaction in sequelize.
updateCreatorDb(resultObj) {
return Promise.map(resultObj.rows, function (result: any) {
return sequelize.transaction({ autocommit: false }).then(function (t) {
return postgresService.updateByIdTrans(result.id, "user_transactions",
{ status: "sucess" }, t).then(function () {
return postgresService.updateByIdTrans(result.to_user, "user",
{ balance: sequelize.literal('balance+' + result.amount) }, t).then(function (data) {
t.commit();
}, function (error) {
t.rollback();
})
}, function (error) {
t.rollback();
});
});
});
}
updateByIdTrans(id: number, model: string, attributes, t: Transaction): Promise<void> {
let promise = new Promise<void>((resolve: Function, reject: Function) => {
logger.info("updating table " + model);
return models[model].update(attributes, { where: { id: id }, returning: true }, { transaction: t })
.then((results) => {
if (results.length > 0) {
logger.info(`Updated Object with id ${id}.`);
resolve(JSON.stringify(results[1][0]));
} else {
logger.info(`Object with id ${id} does not exist.`);
resolve(JSON.stringify(`Object with id ${id} does not exist.`));
}
}).catch((error: Error) => {
logger.error(error.message);
reject(error);
});
});
return promise;
}
If 2nd update statement fails then I want to rollback first update statement also as they are using same transaction object and not committed until 2 nd update statement is success
.with this code if I have an error in 2nd update statement then only 2nd update statement is rolled backed and first update statement get commited.
I am a little confused with the req, res parameters for node and the best way to handle these when I'm using asynchronous calls. A function I currently have is supposed to add an Item to my DB, update some DB models accordingly, and then send a response saying the updates were successful. However, the functions that this function asynchronously calls can send an erroneous response if an error happens. If this happens, I get the error Can't set headers after they are sent, since I am trying to call res.send twice. Would appreciate someone's help in figuring out a more optimal way to handle errors. Thanks!
the main function:
item.author = req.user._id;
item.description = req.query.description;
item.rating = req.query.rating;
item.save()
.then(result => {
// update user's items
UserController.updateItems(req.user._id, result._id);
ItemController.updateItemRating(req.query.outingId, req.query.rating, res);
res.send(result);
})
.catch(error => {
res.send(error);
});
updateItemRating:
export const updateItemRating = (itemId, rating, res) => {
Item.findOne({ _id: itemId }).exec((err, item) => {
if (item === undefined || item === null) {
return res.status(404).send('Item not found; check item ID');
}
Item.findOneAndUpdate(
{ _id: itemId },
{ $set: { rating: rating },
},
(error, item) => {
if (error) {
res.status(404).send('Error updating item with new rating');
}
});
});
};
updateItems:
export const updateItems = (userId, itemId) => {
User.findOneAndUpdate(
{ _id: userId },
{ $push: { items: [itemId] } },
(err, user) => {
console.log('user' + user);
if (err) {
console.log('got an error in updateItems');
}
});
};
Function call to updateItems and updateItemRating both are asynchronous . Response send is getting called multiple time , and also not sure which method send is getting called first . To fix you problem I can suggest you to apply below technique :
Callback : You can pass callback as argument which will do res.send and same callback you can call on error or success condition .
UserController.updateItems(req.user._id, result._id,function(status,message){res.status(status).send(message);});
You can update item rating method like :
export const updateItemRating = (itemId, rating, callback) => {
Item.findOne({ _id: itemId }).exec((err, item) => {
if (item === undefined || item === null) {
callback(404,'Item not found; check item ID');
}
Item.findOneAndUpdate(
{ _id: itemId },
{ $set: { rating: rating },
},
(error, item) => {
if (error) {
callback(404,'Error updating item with new rating');
}else{
callback(200);
}
});
});
};
Async Module : You can synchronize you method call using this module.
Instead of having your update functions send the result, you should throw() an error, that way your outer function will catch the error and you can use that to return it.
Another way to think about this is that your outer function is handling the success case with a res.send(), so it should also be responsible for the res.send of the error case.
The less your database layer knows about the caller the easier it will be to reuse our code.
Create a custom error type to encapsulate the 404:
function NotFoundError(message) {
this.message = (message || "");
}
NotFoundError.prototype = new Error();
Then use that in your inner function:
export const updateItemRating = (itemId, rating, res) => {
Item.findOne({ _id: itemId }).exec((err, item) => {
if (item === undefined || item === null) {
throw new NotFoundError('Item not found; check item ID');
}
Item.findOneAndUpdate(
{ _id: itemId },
{ $set: { rating: rating },
},
(error, item) => {
if (error) {
throw new NotFoundError('Error updating item with new rating');
}
});
});
};
And your main becomes:
item.save()
.then(result => {
// update user's items
UserController.updateItems(req.user._id, result._id);
ItemController.updateItemRating(req.query.outingId, req.query.rating, res);
res.send(result);
})
.catch(error => {
if (error instanceof NotFoundError) {
res.status(404).send(error.message);
}
else {
res.send(error);
}
});