I am trying to send the response when the loop k value is equal to users[0].employees.length, but it's directly moving forward to max length, how do I solve it
Userlist.find(queryObj).exec(function (err, users) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
else {
console.log('found users list', users);
// res.json(users[0].employees);
var k=0;
var resparr=[]
for(var i=0;i<users[0].employees.length;i++){
k++;
User.find({"_id":users[0].employees[i]._id}).exec(function(err,user){
resparr=resparr.concat(user);
console.log("resparray:",k,resparr.length,i,users[0].employees.length)
if(k==users[0].employees.length)
{
console.log("success",resparr)
res.json(resparr);
}
})
}
}
});
You are using an asynchronous function call within a synchronous loop. The loop will always finish before your callback functions are called.
You will need some kind of asynchronous looping. For a native solution you can use Promises:
const getUser = (id) => {
return new Promise((resolve, reject) => {
User.find({ '_id': id })
.exec((err, user) => {
if (err) reject(err);
else resolve(user);
});
});
};
const promises = users[0].employees.map(({ _id: id }) => getUser(id));
Promise.all(promises)
.then(users => res.json(users))
.catch(err);
For utility packages in node.js, I'd recommend the package async for a callback based solution or bluebird for Promises. Both have a .map function which is perfect for this use case.
Related
After searching through countless posts I don't understand why I'm still getting a Promise pending after my awaits. The code below should explain it but I'm trying to pull a MongoDB query of the max value of a column/schema. The console.log within the function is giving me the correct timestamp but I'm trying to pass that out of the inner scope and function to another function.
This is pure NodeJS with only MongoDB imported. Can this be done without any external packages?
export async function getMaxDate() {
var time = MongoClient.connect(url, { useUnifiedTopology: true }, function (err, db) {
if (err)
throw err;
var dbo = db.db(getDB);
dbo.collection(getColl)
.find()
.limit(1)
.sort({ 'timestamp': -1 })
.toArray(function (err, result) {
if (err)
throw err;
time = result[0].time; // THIS IS GIVING THE CORRECT VALUE
console.log(time)
db.close();
});
});
return time
}
export async function getMax() {
var block = await getMaxDate();
return block
}
var t = getMax();
console.log(t); // THIS IS GIVE ME A PROMISE PENDING
getMax() returns a promise, you have to wait for it.
var t = await getMax()
Also getMaxDate uses an async callback that you want to promisify:
export async function getMaxDate() {
return new Promise((resolve,reject) => {
MongoClient.connect(url, { useUnifiedTopology: true }, function (err, db) {
if (err)
return reject(err);
var dbo = db.db(getDB);
dbo.collection(getColl)
.find()
.limit(1)
.sort({ 'timestamp': -1 })
.toArray(function (err, result) {
if(err)
reject(err);
else {
let time = result[0].time; // THIS IS GIVING THE CORRECT VALUE
console.log(time)
db.close();
resolve(time);
}
});
})
});
}
For reference, A is the same thing as B here:
async function A(x) {
if(x)
throw new Error('foo');
else
return 'bar';
}
function B(x) {
return new Promise((resolve,reject)=>{
if(x)
reject(new Error('foo'));
else
resolve('bar');
});
}
Promises came first, then async/await notation was introduced to make common Promise coding practices easier
You can use A and B interchangeably:
async function example1() {
try {
await A(1);
await B(0);
}
catch(err) {
console.log('got error from A');
}
}
async function example2() {
return A(0).then(()=>B(1)).catch((err)=>{
console.log('got error from B');
})
}
I'm fairly new to nodejs and have stumbled into a problem with my code.
The documentation for SQL Server and a guide I found on Youtube both handle their code this way, but after starting to use bycrypt I've noticed my function ends after the request is complete although I'm using .then().
Anyways, here's my code so far:
router.post('/login', (req, res) => {
getLoginDetails(req.body.username, req.body.password).then(result => {
console.log(result);
res.json(result);
})
});
async function getLoginDetails(username, password) {
await pool1Connect;
try {
const request = pool1.request();
request.input('username', sql.NVarChar, username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
if (err) {
return ({err: err})
}
if (result.recordset.length > 0) {
bcrypt.compare(password, result.recordset[0].user_password, (err, response) => {
if (response) {
console.log(result.recordset);
return(result.recordset);
} else {
return({message: "Wrong password or username!"})
}
})
return(result)
} else {
return({message: "user not found!"})
}
})
} catch (err) {
return err;
}
}
I tried logging both the request and the return value from the function getLoginDetails and the request log came faster, so I assume it's not waiting for the program to actually finish and I can't figure out why...
Sorry if that's obvious, but I'd love to get some help here!
EDIT:
router.post('/login', async (req, res) => {
// res.send(getLoginDetails(req.body.username, req.body.password))
await pool1Connect
try {
const request = pool1.request();
request.input('username', sql.NVarChar, req.body.username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
console.log(result);
bcrypt.compare(req.body.password, result.recordset[0].user_password, (err, response) => {
if (response) {
res.send(result);
} else {
res.send('wrong password')
}
})
//res.send(result)
})
} catch (err) {
res.send(err);
}
});
This code works, but when I tried to encapsulate it in a function it still didn't work.
#Anatoly mentioned .query not finishing in time which makes sense, but I thought mssql .query is an async function?
Your problem arises from an wrong assumption that callbacks and promises are alike, but on the contrary callbacks don't "respect" promise/async constructs
When the program hits the bottom of getLoginDetails the progrm execution has already split into 2 branches one branch returned you the (empty) result whereas the other one still busy with crypto operations.
Though it is true that an async function always returns a promise but that doesn't cover any future callbacks that might execute inside it. As soon as node reaches the end of function or any return statement the async function's promise get resolved(therefore future callbacks are meaningless), what you can do instead is handroll your own promise which encampasses the callbacks as well
router.post('/login', (req, res) => {
getLoginDetails(req.body.username, req.body.password))
.then((result)=>{
res.send(result);
})
.catch((err)=>{
res.send(err);
})
});
async function getLoginDetails(username, password) {
await pool1Connect
return new Promise( (resolve,reject) => {
try {
const request = pool1.request();
request.input('username', sql.NVarChar, username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
console.log(result);
bcrypt.compare(password, result.recordset[0].user_password, (err, response) => {
if (response) {
resolve(result);
} else {
resolve('wrong password')
}
})
})
} catch (err) {
reject(err);
}
});
}
You didn't return any result to getLoginDetails. Either you use async versions of request.query and bcrypt.compare (if any) or wrap request.query to new Promise((resolve, reject) like this:
const asyncResult = new Promise((resolve, reject) => {
request.query('SELECT ...
...
if (err) {
resolve({err: err}) // replace all return statements with resolve calls
}
...
})
const queryResult = await asyncResult;
Here I am trying to retrieve objects and push them into the array. For some reason there is only one record being pushed into the file when it should contain more objects. Can you help me out with this or let me know where I am going wrong? Here is my code:
exports.createjson = (req, res, next) => {
try {
var myPromise = () => {
// ...
};
var callMyPromise = async () => {
const responsearray = [];
var result = await myPromise();
return new Promise((resolve, reject) => {
result.forEach(element => {
NewsModel.findOne({ _id: element.newsId }).exec(
async (err, result) => {
if (err) {
throw err;
}
reportsModel
.findOne({
$and: [
{ userId: req.query.userId },
{ newsId: element.newsId }
]
})
.exec((err, newsResult) => {
if (err) {
throw err;
}
// console.log(newsResult);
var response = {
newsId: element.newsId,
title: result.title,
collection: result.group,
belivibalityIndex: element.belivibalityIndex,
priorknowledge: element.priorknowledge,
readingTime: element.readingTime,
userId: element.userId,
comment: element.comment,
report: newsResult !== null ? newsResult.feedback : null
};
// #all object pushed and displayed in console
responsearray.push(response);
console.log(response);
console.log(responsearray.length);
// let data = JSON.stringify(responsearray);
// #here is the issue // fs.writeFileSync("abc.json", data, null, null, flag = 'a');
return responsearray;
});
}
);
});
});
};
callMyPromise().then(function(responsearray) {
res.json(responsearray);
});
} catch (error) {
next(error);
}
};
You're not quite using Promises properly. For example, you create a Promise object but never call the resolve/reject functions. In the forEach loop you are calling functions that use callbacks and when that work is done you can resolve the promise you're wrapping it in.
Also you're calling res.json and writing the file (though it's commented out) while you're in the forEach loop. That means res.json will get called multiple times, which is not allowed. You can only have one response from an http request.
I restructured the code so that it collects each promise in an array of Promises then waits for all of them to resolve. Only after all of the work is done, we can write the file and call res.json to complete the http request.
exports.createjson = async (req, res, next) => {
const responsearray = [];
var elements = await myPromise();
var promises = []; // collect a bunch of promises to wait on
elements.forEach(element => {
// one promise per element that resolves when response is on the array
var promise = new Promise(function(resolve, reject) {
NewsModel.findOne({ _id: element.newsId }).exec((err, result) => {
if (err) { return reject(err); }
reportsModel
.findOne({
$and: [{ userId: req.query.userId }, { newsId: element.newsId }]
})
.exec((err, newsResult) => {
if (err) { return reject(err); }
var response = { /* response body */ };
responsearray.push(response);
console.log(response);
console.log(responsearray.length);
// complete the promise now that the response is on the array
return resolve();
});
});
});
// collect each promise in an array so we can wait for them all
promises.push(promise);
});
// wait for all the work to complete
await Promise.all(promises).catch(err => next(err));
// write the responsearray to a file as json
let data = JSON.stringify(responsearray);
fs.writeFileSync("abc.json", data);
return res.json(responsearray);
};
I also removed the try/catch block since the Promise allows you to use .catch in a cleaner way. It simplifies the nesting which makes it easier to read.
The key takeaway here is the general structure:
// get your array to work with
var array = await someFunction()
var manyPromises = []
var manyResults = []
// for each thing in the array create a promise
array.forEach( thing => {
manyPromises.push( new Promise((resolve,reject) => {
doSomething(thing, (err, result) => {
if (err) return reject(err);
// store the results in the array and resolve the promise
manyResults.push(result)
return resolve();
});
});
});
// wait for all promises in manyPromises to complete
await Promise.all(manyPromises).catch(err => return next(err));
// now the many promises are done and manyResponses are ready
saveResponsesToFile(JSON.stringify(manyResponses))
return res.json(manyReponses)
I have the following function:
const findUserByEmail = (email) => {
var user
MongoClient.connect(url, (err, db) => {
const dbo = db.db('users')
dbo.collection('users').findOne({email: email}, (err, result) => {
if (err) throw err
else return result
})
db.close()
}).then((user) => {
return user
})
}
I am trying to return the value of user so that findUserByEmail(email)=user but can't figure out how. I have tried using async/await and promises to return this value to get around Node's asynchronous nature however in each I could not get the value to return to the main function. In this case, return user returns the user to the then() function, which is not correct.
Any help would be much appreciated.
Your very close but there is one thing your missing with your function findUserByEmail it needs to return a Promise. With a Promise you can call resolve in the future with the result from findOne. This will also change how you consume the findUserByEmail function.
Example
const findUserByEmail = (email) => {
return new Promise((resolve, reject) => {
MongoClient.connect(url, (mongoError, db) => {
if (mongoError) {
return reject(mongoError)
}
const dbo = db.db('users')
dbo.collection('users').findOne({ email: email }, (findError, user) => {
if (findError) {
return reject(findError)
}
db.close();
return resolve(user)
})
})
})
}
To consume this function you can use Promise.then(user => ) or the preferred approach of using async/await.
Consuming using .then()
findUserByEmail("email.com").then(user => {
// do something
}).catch(err => {
// do something
})
Consuming using async/await
async function test() {
try {
const user = await findUserByEmail("email.com");
// do something
} catch (error) {
// do something
}
}
Insted of using setTimeout, what should I use after foreach complete?
app.post('/grid', function(req, res){
getResults(req.body.idarray, function(callback){
res.send(callback);
});
});
function getResults(userIds, callback) {
var totalresult = [];
userIds.forEach(function (user) {
sequence
.then(function (next) {
db.query('SELECT given FROM books WHERE user_id = ?', [user.userId], function (err2, result) {
if (err2) throw err2;
next(err, result);
});
})
.then(function (next, err, books) {
db.query('SELECT received FROM encycs WHERE user_id = ?', [user.userId], function (err3, result2) {
if (err3) throw err3;
next(err, result2, books);
});
})
.then(function (next, err, books, encycs ) {
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
next();
});
});
setTimeout(function() {
console.log(totalresult); // output ok.
return callback(totalresult); // returning as expected
}, 2000);
}
I dont know what totalresult.length is. So i can't check the length.
So, according to your use case you need to call callback somehow and pass totalresult into it, because that what your external code, code in the route expected.
To do that, you can call callback before calling next of the third .then statement. Like that.
...
.then(function (next, err, books, encycs ) {
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
callback(totalresult);
next();
//console.log(totalresult); //output OK.
});
This might work.
Update 1
It is hard to follow with your code. Can't catch up the logic of it. I would propose you Promises approach. I prepared that solution, that might work. It might contain little errors, but it represents the main idea of what you are trying to achieve, and how it can be done.
app.post("/grid", (req, res) => {
getResults(req.body.idarray)
.then(data => {
res.status(200).json(data);
})
.catch(err => {
console.error("Error occured", err);
res.status(500).json(err);
});
});
function getResults(userIds) {
let promises = userIds.map(loadCalculation);
//this will wait until all loadings are done
return Promise.all(promises);
}
function loadCalculation(user) {
//parallel loading of the books and encycs
return Promise.all([loadBooks(user), loadEncycs(user)])
.then(results => {
let books = results[0];
let encycs = results[1];
let totalresult = [];
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
return totalresult;
});
}
function loadBooks(user) {
return makeQuery('SELECT given FROM books WHERE user_id = ?', user);
}
function loadEncycs(user) {
return makeQuery('SELECT received FROM encycs WHERE user_id = ?', user);
}
function makeQuery(query, user) {
return Promise((resolve, reject) => {
db.query(query, [user.userId], function (err, result) {
if(err) {
reject(err);
} else {
resolve(result);
}
});
});
}
Please, note that this is not really performant way to load the data from database, at least, I'm sure that you there is a possibility to load all the books and encycs with a single query, because you are using SQL, and it is really flexible language.