how to rewrite with callback for promised result - javascript

There is a function to generate a string and return it if it is in the User table.
function generateFortToken(len) {
let rs;
rs = randomstring.generate(len);
User.findOne({where: {fort_token: rs}})
.then(result => {
console.log("hit is : ", result);
if (!result) //need to return rs. but HOW?
})
.catch(err => {
console.log("Error search for fort token : ", err.message);
});
}
This generateFortToken is in module helper and is called from parent function like this:
user.fort_token = helper.generateFortToken(20);
This code does not work as many online posts pointed out since findOne returns a promise. But I am having hard time to rewrite it with callback to return the value of token generated.

The code you came up with is fine, but it can be improved. In particular, you've fallen into to the Promise constructor antipattern. In short, you're constructing a new promise (await new Promise(next => User.findOne(...))) when you can use the promise returned by User.findOne directly:
async function generateFortToken(len) {
for (let rs;; rs = randomstring.generate(len)) {
try {
if (await User.findOne({ where: { fort_token: rs }})) {
return rs;
}
}
catch (err) {
console.log('Error search for fort token : ', err.message);
}
}
}

Solved the problem with the code below:
generateFortToken : async function(len) {
let rs, bs, lp = true;
while (lp) {
rs = randomstring.generate(len);
await new Promise(next => {
User.findOne({where : {fort_token : rs}})
.then(result => {
if(!result) lp = false;
next();
})
.catch(err => {
console.log("Error search for fort token : ", err.message);
//next();
});
});
}
return rs;
},
In parent function:
user.fort_token = await helper.generateFortToken(20);
Inspired by Fernando Carvajal reply to the post.

Related

While loop not terminating even after the 'return'

I am using the while loop to iterate through an array and trying to terminate the loop using the return (I tried foreach before which doesn't terminate for return but the while loop should terminate with the use of return).
Can someone help me why this is happening?
Here is my code:
req.on("data", async data => {
let fetchedData = JSON.parse(data.toString())
let index=0
while(index<fetchedData.length) {
const invoice = fetchedData[index]
// fetchedData.forEach((invoice, index) => {
console.log('index',index)
await new Promise((resolve, reject) => {
db.query(
`SELECT * FROM payments WHERE invoice_id = '${invoice.id}' and status = 1`,
(err, results) => {
if (err) {
resolve()
return res.json({
status: "error",
message: "something went wrong!",
})
} else {
if (results.length > 0) {
resolve()
console.log('returned')
return res.json({
status: "error",
message: `Payment is already done for invoice number ${invoice.invoiceNumber} ! Please delete the payment first.`,
})
} else if (index == fetchedData.length - 1) {
for(let i =0; i<fetchedData.length; i++){
db.query(`UPDATE invoices SET status = 0 WHERE id = '${fetchedData[i].id}'`, (err, results) => {
if (err) {
resolve()
return res.json({
status: "error",
message: "something went wrong!",
})
} else{
deletedInvoices.push(fetchedData[i].id)
if(i == fetchedData.length - 1){
console.log('deleted')
return res.json({
status: "success",
message: "invoice deleted",
deletedInvoices: deletedInvoices
})
}
resolve()
}
})
}
}
}
}
)
})
index++;
}
})
output:
for an array with the length of 2:
index 0
returned
index 1
returned
(it also throws an error because it is sending a response two times!
Your return statements are not part of the while loop that is in the req.on callback, but are part of dq.query callback functions. So they don't relate to the loop.
An approach is to promisify the db.query function. Check the documentation of your database API, as there might already be a promise-returning alternative for this method. But if not, you can use this generic function instead:
const asyncQuery = (db, sql) =>
new Promise((resolve, reject) =>
db.query(sql, (err, results) =>
err ? reject(err) : resolve(results)
)
);
Now you can put those return statements directly in your loop, and they will exit the db.req callback:
req.on("data", async data => {
try {
const fetchedData = JSON.parse(data.toString())
for (const {id, invoiceNumber} of fetchedData) { // Simpler loop
const results = await asyncQuery(db, `SELECT * FROM payments WHERE invoice_id = '${id}' and status = 1`)
if (results.length > 0) {
// Now we are in the req.on callback, so this will exit it:
return res.json({
status: "error",
message: `Payment is already done for invoice number ${invoiceNumber} ! Please delete the payment first.`,
});
}
}
for (const {id} of fetchedData) {
await asyncQuery(db, `UPDATE invoices SET status = 0 WHERE id = '${id}'`);
}
res.json({
status: "success",
message: "invoice deleted",
deletedInvoices: fetchedData.map(({id}) => id)
});
} catch (err) {
res.json({
status: "error",
message: "something went wrong!",
});
}
});
In short: the return statement applies to the scope of the function that it is in. And your loop is in an outer scope.
Possible solution
You could define another variable in the same scope as you while loop, check it in each iteration, and when you want an inner scope to end the loop, you not only return but also set that variable.
Something like:
// ...
let index=0
let shouldLoopContinue = true
while(shouldLoopContinue && index<fetchedData.length) {
// ...
and:
// ...
if (err) {
resolve()
shouldLoopContinue = false
return res.json({
status: "error",
message: "something went wrong!",
})
}
// ...
And also in any other place that returns and should stop the loop.
Explanation
In your case:
the scope you call return from is the callback function passed as a parameter to db.query(...).
db.query(...) itself is in the scope of the callback function passed to new Promise(...)
... which is in the same scope as your while loop
So when you call return the way you do now, you only end the execution of that inner callback.

Node.js promise fails intermittently, even when handled

I'm learning to use MongoDB by creating a simple blog app. However, a portion of my code that saves a given post seems to give problems with promises occasionally, but not always, and whether the code succeeds simply seems to be luck.
Each post in my database is stored with the following schema:
{
title: String,
author: String,
body: String,
slug: String,
baseSlug: String,
published: { type: Boolean, default: false }
}
The slug defines the link used to access the blog post, and is automatically generated based upon the title of the blog post. However, if article titles are duplicates, the slug will have a number added to the end to differentiate it from similar articles, while the baseSlug will remain the same. For example:
I create the post "My first post", and it is assigned the baseSlug of "my-first-post". Because no other posts have the same baseSlug, the slug is also set to be "my-first-post".
I create another post called "My first post", and it is assigned the baseSlug of "my-first-post". However, because another post has the same baseSlug, it is assigned the slug "my-first-post-1".
To create this behavior, I wrote the following addpost route in Express:
app.post("/addpost", (req, res) => {
let postInfo = req.body;
for (key of Object.keys(postInfo)) {
if (postInfo[key] == "true") postInfo[key] = true;
}
let slug = postInfo.title
.toLowerCase()
.split(" ")
.filter(hasNumber) // return /\d/.test(str);
.slice(0, 5)
.join("-");
postInfo.slug = slug;
var postData;
Post.find({ baseSlug: postInfo.slug }, (error, documents) => {
if (documents.length > 0) {
let largestSlugSuffix = 0;
for (let document of documents) {
var fullSlug = document.slug.split("-");
var suffix = fullSlug[fullSlug.length - 1];
if (!isNaN(suffix)) {
if (parseInt(suffix) > largestSlugSuffix) {
largestSlugSuffix = suffix;
}
}
}
largestSlugSuffix++;
postInfo.baseSlug = postInfo.slug;
postInfo.slug += "-" + largestSlugSuffix;
} else {
postInfo.baseSlug = postInfo.slug;
}
postData = new Post(postInfo);
})
.then(() => {
postData
.save()
.then(result => {
res.redirect("/");
})
.catch(err => {
console.log(err);
res.status(400).send("Unable to save data");
});
})
.catch(err => {
console.log(err);
res.status(400).send("Unable to save data");
});
});
This code seems to work most of the time, but sometimes it fails, and outputs the following:
TypeError: Cannot read property 'save' of undefined
at C:\Users\User\BlogTest\app.js:94:18
at processTicksAndRejections (internal/process/task_queues.js:94:5)
(For reference, line 94 in my file is postData.save())
I suspect it is because the main body of the function takes longer than it should to execute, and the postData variable is not yet defined. However, postData.save() should not be executed until the promise finishes, because of the .then() callback function.
Why is my code behaving like this? Is there any way to fix it?
The issue is that you are mixing promises with callbacks and closures. That's not how this is intended to work.
When you chain promises, whatever you return in the first promise handler will be added as an input to the next one. And if you return a promise, that promise will be resolved first before being sent to the next thenable.
So you need to return promises from your promises, like this:
app.post("/addpost", (req, res) => {
let postInfo = req.body;
for (key of Object.keys(postInfo)) {
if (postInfo[key] == "true") postInfo[key] = true;
}
let slug = postInfo.title
.toLowerCase()
.split(" ")
.filter(hasNumber) // return /\d/.test(str);
.slice(0, 5)
.join("-");
postInfo.slug = slug;
// var postData; <-- Don't do that
Post.find({ baseSlug: postInfo.slug })
.then((documents) => {
if (documents.length > 0) {
let largestSlugSuffix = 0;
for (let document of documents) {
var fullSlug = document.slug.split("-");
var suffix = fullSlug[fullSlug.length - 1];
if (!isNaN(suffix)) {
if (parseInt(suffix) > largestSlugSuffix) {
largestSlugSuffix = suffix;
}
}
}
largestSlugSuffix++;
postInfo.baseSlug = postInfo.slug;
postInfo.slug += "-" + largestSlugSuffix;
} else {
postInfo.baseSlug = postInfo.slug;
}
return new Post(postInfo);
// We could actually have called postData.save() in this method,
// but I wanted to return it to exemplify what I'm talking about
})
// It is important to return the promise generated by postData.save().
// This way it will be resolved first, before invoking the next .then method
.then( (postData) => { return postData.save(); })
// This method will wait postData.save() to complete
.then( () => { res.redirect("/"); })
.catch( (err) => {
console.log(err);
res.status(400).send("Unable to save data");
});
});
It can be greatly simplified with async/await:
app.post("/addpost", async (req, res) => {
try {
let postInfo = req.body;
for (key of Object.keys(postInfo)) {
if (postInfo[key] == "true") postInfo[key] = true;
}
let slug = postInfo.title
.toLowerCase()
.split(" ")
.filter(hasNumber)
.slice(0, 5)
.join("-");
postInfo.slug = slug;
let documents = await Post.find({ baseSlug: postInfo.slug });
if (documents.length > 0) {
let largestSlugSuffix = 0;
for (let document of documents) {
var fullSlug = document.slug.split("-");
var suffix = fullSlug[fullSlug.length - 1];
if (!isNaN(suffix)) {
if (parseInt(suffix) > largestSlugSuffix) {
largestSlugSuffix = suffix;
}
}
}
largestSlugSuffix++;
postInfo.baseSlug = postInfo.slug;
postInfo.slug += "-" + largestSlugSuffix;
} else {
postInfo.baseSlug = postInfo.slug;
}
let postData = new Post(postInfo);
await postData.save();
res.redirect("/");
} catch (err) {
console.log(err);
res.status(400).send("Unable to save data");
};
});
You are mixing callbacks and promises and while it may do something, I'm not sure what it will do exactly. You should pick one or the other and not mix them as much as possible. I would recommend picking promises if you are using a language that supports async/await, otherwise callbacks.
So for example your outter handler could be an async function
app.post("/addpost", async (req, res) => {
//...
})
Your real bug is in handling Post.find you are handling it somewhat with a callback and somewhat with a promise, and probably whats happening is that its random which one will get called first the callback or the promise resolution. Instead of both you should just do this now that you have an async function:
try {
const posts = await Post.find({ baseSlug: postInfo.slug });
// stuff you were doing in the callback
const post = new Post(postInfo)
// Now the promise code
await post.save()
// success!
res.redirect("/");
} catch (err) {
// With an async function you can just catch errors like normal
console.log(err);
res.status(400).send("Unable to save data");
}
If you're not using webpack or typescript and cannot target es7 then and thus cannot use async/await then I would recommend just using callbacks, do not use .then or .catch and that would look more like:
function error(err) {
console.log(err)
res.status(400).send("Unable to save data")
}
Post.find({ baseSlug: postInfo.slug }, (err, documents) => {
if (err) return error(err)
// stuff you're doing in the callback now
const post = new Post(postInfo)
post.save((err) => {
if (err) return error(err)
// success!
res.redirect("/");
})
})

Use async await inside mapping function

In my Node Express App, I want to get all comments for a lesson and modify each comment by adding a fullname field to each comment. For getting full name of a user, I have defined findFullNameByUserId function in UserController.js. I use findAllCommentsByLessonId() in CourseController.js as follows. However, when I'm using it, I always get an empty array. How can I use findFullNameByUserId in findAllCommentsByLessonId() so that fullname field can be added to each comment object?
CourseController.js
findAllCommentsByLessonId: async (courseId,lessonId,callback) => {
Course.find({_id: courseId}, function(err, course) {
if (err) {
callback(err, null);
} else {
const lesson = course[0].lessons.filter(l => l._id !== lessonId)
const comments = lesson[0].comments.map( async c => {
try{
const name = await UserController.findFullNameById(c.user)
return (
{
userId: c.user,
name: name,
content: c.content
}
)
}
catch(err){
console.log(err)
}
})
// console.log(comments) --> This always prints [{}]
callback(null, comments)
}
});
}
UserController.js
module.exports = {
findFullNameById: async (userId) => {
await User.find({_id: userId}, function(err, user) {
if (err) {
console.log(err)
} else {
return user[0].name+" "+( user[0].lname ? user[0].lname : "")
}
});
}
}
in CourseController.js either you can use async-await or you can use callback
async-await way :
findAllCommentsByLessonId: async (courseId,lessonId) => {
let course = await Course.findOne({_id: courseId});
if (course){
let lesson = course.lessons.find(l => l._id == lessonId);
let comments = [];
for(let comment of lesson.comments){
let name = await UserController.findFullNameById(comment.user);
comments.push({userId: comment.user,name: name,content: comment.content});
}
return comments;
}else{
return [];
}
}
callback way :
findAllCommentsByLessonId: (courseId,lessonId,callback) => {
Course.findOne({_id: courseId},function(err, course) {
if (err) {
callback(err, null);
} else {
let lesson = course.lessons.find(l => l._id == lessonId);
let comments = lesson.comments;
comments.map((comment)=>{
UserController.findFullNameById(comment.user).then(name=>{
return {userId: comment.user,name: name,content: comment.content};
});
});
callback(null, comments);
}
});
}
Start by dropping callbacks and actually using promises for await. You haven't specified what find is, but chances are it's a modern library supporting promises. So write
async function findAllCommentsByLessonId(courseId, lessonId) {
const [course] = Course.find({_id: courseId};
const lesson = course.lessons.find(l => l._id !== lessonId);
const comments = lesson.comments.map(async c => {
const name = await UserController.findFullNameById(c.user)
return {
userId: c.user,
name,
content: c.content
};
});
}
module.exports.findFullNameById = async (userId) => {
const [user] = await User.find({_id: userId});
return user.name + " " + (user.lname ? user.lname : "");
};
Then you'll notice that comments is not an array of users, but rather an array of promises for users, so wrap it in a await Promise.all(…) call.
As #SuleymanSah commented, I tried to use .populate and it worked well. I think this is the correct approach as for the exact reasons he's pointed out. The following is how I did it:
Lesson.findOne({ _id: lessonId }).
populate('comments.user').
exec(function(err, lesson) {
if (err) {
console.log(err);
return callback(err, null);
}
if (!lesson) {
console.log("No record found");
return callback(err, null);
}
return callback(null, lesson.comments);
});

Is there anyway to run a specific await aciton prior to others within in the same function?

await ipfs.files.add(this.state.file, (err,result) => {
if(err){
console.log(err);
return
}
console.log('profile hash ' + result[0].hash);
this.setState({profilePic : result[0].hash , continue : true});
});
this.setState({loading : true,visible : 'true'});
console.log('gender value is ' + this.state.gender);
const accounts = await web3.eth.getAccounts();
console.log( 'the value of profilepic is ' + this.state.profilePic);
if(this.state.profilePic == '')
{
console.log('waiting');
}else{
try{
this.setState({continue : false});
console.log('profile hash again ' + this.state.profilePic);
await Patient.methods.insertPatient(
accounts[0],
this.state.surname,this.state.givenname,
this.state.gender,this.state.age,
this.state.email,this.state.language,
this.state.nationality,this.state.phone,
this.state.medicalno,this.state.profilePic)
.send({
from : accounts[0],
});
}
catch (e) {
console.log(e);
} finally {
this.setState({loading : false,visible : 'false'});
}
}
I have this await ipfs add file to run first and then the second await takes the result of the first await and then proceed.
I want the second await to wait if the first await hasnt completed yet
Thank you
In order for await to have any meaningful effect, you need to await a promise. It's not going to throw any exceptions if you await a non-promise, but it also won't delay moving on to the next code.
To take code that's written with callbacks and turn it into a promise, you'll need to wrap it in a new promise. For your case, that would probably look like this:
await new Promise((resolve, reject) => {
ipfs.files.add(this.state.file, (err,result) => {
if(err){
reject(err);
return;
}
console.log('profile hash ' + result[0].hash);
this.setState({profilePic : result[0].hash , continue : true});
resolve(result);
});
});
Now that you're awaiting a promise, the execution of this async function will pause until that promise is resolved. Code later in the async function will not run until then.
If you're doing a fair amount of calls to ipfs.files.add, you might want to make a helper function that does the promise creation for you. As in:
function add(file) {
return new Promise((resolve, reject) => {
ipfs.files.add(file, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
}
}
// to be used as:
const result = await add(this.state.file);
console.log('profile hash ' + result[0].hash);
this.setState({profilePic : result[0].hash , continue : true});

Why return not works in Mongoose findOne method?

I try find one item by name, then update table and return updated result, but return didn't work. My code:
class addSongsToArtist {
constructor(artistName) {
Artist.findOne({
name: artistName
}).exec((err, data) => {
if (err) console.log(err);
data.name = 'updated name'
data.save();
return data // * not work
});
}
}
Into exec method I see data with correct result, and into mongo console result saved. But return not works. I tried save changes into external variable and return result in Promise, but it not work too.
Why it not works?
You use .save async function as sync function, try this instead :
constructor(artistName) {
Artist.findOne({
name: artistName
}).exec((err, data) => {
if (err || !data){
console.log(err);
return null;
}
else
{
data.name = 'updated name'
data.save(function(err, savedDatas)
{
if(err || !savedDatas)
{
return null;
}
else
{
return savedDatas; // * not work
}
});
}
});
I have solution. Problem with construct method which return new object instance.
Worked code:
class addSongsToArtist {
constructor(artistName) {
this.result = false
Artist.findOne({
name: artistName
}).exec((err, data) => {
if (err) console.log(err);
data.name = 'updated name'
data.save();
this.result = data
});
}
/**
* need call in promise after call new addSongsToArtist()
*/
getData() {
return this.result
}
}
And get data:
let findOne;
Promise.resolve()
.then(()=>{
findOne = new addSongsToArtist()
})
.then(()=>{
findOne.getData()
});

Categories

Resources