My counter is reset after exiting the loop - arrays

My counter count resets after each time the second forEach loop exits but it is not meant to be reset, as when I console log inside the second loop the numbers are correct but with no luck.
let arr = [];
var count;
result.forEach(server => {
count = 0;
sql.query(`SELECT * FROM users WHERE server='${server.server}';`, async (error, results) => {
if (error) {
console.log(error);
}
results.forEach(user => {
if(user.key_deactivated == 'false') {
count++;
}
});
console.log(count);
});
arr.push({
server: server.server,
name: server.name,
plan_id: server.plan_id,
mb_stripe: server.mb_stripe,
description: server.description,
key_stock: server.key_stock,
channel: server.channel,
active: count
});
});
res.render('pages/administrator', {result: result, user: req.user, arr: arr});

you are using callback function and callback function is part of asynchronous behavior. means js pushes it to a callback queue to execute it later. so the count value is being accessed before the callback can change it.
let arr = [];
var count;
result.forEach(server => {
count = 0;
sql.query(`SELECT * FROM users WHERE server='${server.server}';`, async (error, results) => {
if (error) {
console.log(error);
}
results.forEach(user => {
if(user.key_deactivated == 'false') {
count++;
}
});
console.log(count);
arr.push({
server: server.server,
name: server.name,
plan_id: server.plan_id,
mb_stripe: server.mb_stripe,
description: server.description,
key_stock: server.key_stock,
channel: server.channel,
active: count
});
res.render('pages/administrator', {result: result, user: req.user, arr: arr});
});
});
for more information on callback check this out. https://developer.mozilla.org/en-US/docs/Glossary/Callback_function

Welcome to callback hell.
"Abandon all hope, ye who enter here."
Dante Alighieri
The less poetic description of your issue is that you are using callbacks and expect them to be executed before the code you see after them. But it is not how they work. Let me explain what your code does:
It iterates result and for each instance it sets count to 0 and asynchronously waits for a query to execute and while waiting for the query to execute, without actually waiting it to be executed it pushes objects (containing count) to arr. But count is not yet refreshed yet, the queries are still being executed. Just put this code
arr.push({
server: server.server,
name: server.name,
plan_id: server.plan_id,
mb_stripe: server.mb_stripe,
description: server.description,
key_stock: server.key_stock,
channel: server.channel,
active: count
});
just after your console.log.

Related

Discord poll bot async issues

I am trying to make a poll command for a discord bot in which the user chooses a number of options in the first command (ie '!poll 4') and then chooses the questions and the options. I am having some issues getting the bot to wait for a response before it moves on to the next option in the loop. When I try and use await in the loop it says I cannot use await because it's not an async function, but it is an async function I think. I am very inexperienced with this so I am sure it is a simple error or probably multiple. If anyone can give me advice on a way to make the loop work as intended and ask for each option I would appreciate it. Also is there a way to add if statements to do addFields to an embed? Here is my code:
const Discord = module.require('discord.js');
module.exports = {
name: 'poll',
async execute(message, args) {
function isNumber(n) { return !isNaN(parseFloat(n)) && !isNaN(n - 0) }
if(isNumber(args[1])){
if(args[1]<2) return message.channel.send('Please choose a higher number of options for the poll :)');
if(args[1]>10) return message.channel.send('Please choose a lower number of options for the poll :)');
const filter = response => {
if(!response.author.bot) return response;
};
var question;
var options;
message.channel.send('What question would you like to ask?').then(() => {
message.channel.awaitMessages(filter, { max: 1, time: 15000})
.then(collected => {
question = `${collected.first()}?`;
message.channel.send('Question: ' + question);
for (var i = 0; i < args[1]; i++) {
message.channel.send('What is option ' + (i + 1) + '?').then(() => {
message.channel.awaitMessages(filter, { max: 1, time: 15000})
.then(collected => {
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
})
.catch(collected => {
message.channel.send('Poll has timed out.');
});
})
}
})
.catch(collected => {
message.channel.send('Poll has timed out.');
});
const embed = new Discord.MessageEmbed()
.setColor(3447003)
.setTitle(question)
.setDescription('choose an option')
/*
if (options[0]) .addField('1️⃣:' + option[0])
if (options[1]) .addField('2️⃣:' + option[1])
if (options[2]) .addField('3️⃣:' + option[2])
if (options[3]) .addField('4️⃣:' + option[3])
if (options[4]) .addField('5️⃣:' + option[4])
if (options[5]) .addField('6️⃣:' + option[5])
if (options[6]) .addField('7️⃣:' + option[6])
if (options[7]) .addField('8️⃣:' + option[7])
if (options[8]) .addField('9️⃣:' + option[8])
if (options[9]) .addField('🔟:' + option[9])
*/
message.channel.send(embed).then(embedMessage => {
if (options[0]) embedMessage.react('1️⃣');
if (options[1]) embedMessage.react('2️⃣');
if (options[2]) embedMessage.react('3️⃣');
if (options[3]) embedMessage.react('4️⃣');
if (options[4]) embedMessage.react('5️⃣');
if (options[5]) embedMessage.react('6️⃣');
if (options[6]) embedMessage.react('7️⃣');
if (options[7]) embedMessage.react('8️⃣');
if (options[8]) embedMessage.react('9️⃣');
if (options[9]) embedMessage.react('🔟');
});
});
}
}
}
Since you say you are trying to use await in your loop, let me take the function it is contained in out from your snippet, format it a little, and try to do some explaining. Disclaimer: I am no expert, so I am learning as well.
.then(collected => {
question = `${collected.first()}?`;
message.channel.send(`Question: ${question}`);
for (var i = 0; i < args[1]; i++) {
message.channel.send(
`What is option ${i + 1}?`
).then(() => {
message.channel.awaitMessages(filter, {
"max": 1,
"time": 15000,
}).then(collected => {
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
}).catch(collected => {
message.channel.send("Poll has timed out.");
});
});
}
});
But before that, as the first inner .then() still returns a Promise, you can chain the second inner .then() in the outer scope to avoid nesting them too deep, and leave a single .catch() at the end. On that note, it would probably be more accurate to call the catch's parameter something like error. So here's the new snippet:
.then(collected => {
question = `${collected.first()}?`;
message.channel.send('Question: ' + question);
for (var i = 0; i < args[1]; i++) {
message.channel.send(
`What is option ${i + 1}?`
).then(() => {
message.channel.awaitMessages(filter, {
"max": 1,
"time": 15000,
});
}).then(collected => { // Change .then() chaining
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
}).catch(error => { // Change parameter name
message.channel.send("Poll has timed out.");
});
}
})
What's happening now is that each iteration is running one after the other immediately. You .send() a whole bunch of messages which each return a Promise, and off that Promise, you pass a callback function to .then() which runs once each Promise resolves into a Message. That callback implicitly returns the result of .awaitMessages(), which is also a promise, and once that resolves, the next callback in the next .then() runs with the value of whatever the previous promise resolved to passed in as an argument, and so on.
Alright, so you want to the entire Promise chain to finish processing and resolve before proceeding to the next iteration, right? You can use the await keyword to suspend progress in the relevant anonymous function, until its associated promise-based operation either resolves or rejects. The catch is that that function has to be marked with the async keyword, and in your code, that is not actually the case, you are just making use of Promises and callback functions (regarding "but it is an async function I think"). So, let's add both the aforementioned keywords:
.then(async collected => { // Mark as async
question = `${collected.first()}?`;
message.channel.send('Question: ' + question);
for (var i = 0; i < args[1]; i++) {
await message.channel.send( // Wait for this entire Promise chain to resolve before proceeding
`What is option ${i + 1}?`
).then(() => {
message.channel.awaitMessages(filter, {
"max": 1,
"time": 15000,
});
}).then(collected => {
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
}).catch(error => {
message.channel.send("Poll has timed out.");
});
}
})
That should cause your desired behaviour, though my edits may have syntax errors as I have not ran it myself. If I got something wrong, please do comment.
You can read more here:
Using async-await
Using Promises

How to return array as an output after the execution of forEach loop?

I have an array in which project _id is stored in the user collection. I'm initially fetching that array and then I'm trying to retrieve the data corresponding to the _id (stored in user collection) stored if projects collection. The data retrieved is pushed on to the array and that array is returned as an output.
But I'm unable to do so. An empty array is being returned.
var projectInfo= new Array();
users.post('/retrieveProjects', function(user, res, next) {
MongoClient.connect(url, function(err,db) {
if (err) throw err;
var dbo = db.db("EMWorks");
dbo.collection("users").findOne({_id:user.body.userid})
.then(response => {
if(response)
{
console.log(response);
(response.project).forEach(element => {
dbo.collection("projects").findOne({_id:element})
.then(respo => {
console.log(respo);
projectInfo.push(respo);
});
console.log(projectInfo) ;
});
}else{
console.log("No Projects created yet...!");
}
});
});
});
Output of the code
The problem is that dbo.collection("projects").findOne is async and returns a promise.
The forEach loop runs and generates these promises for each elemnt, but does not wait for its execution.
The promises are not resolved when the loop finishes and an empty array is returned.
You can wait for the promises to be executed and return the array only after that:
const promises = [];
(response.project).forEach(element => {
promises.push(dbo.collection("projects").findOne({_id:element})
.then(respo => {
console.log(respo);
projectInfo.push(respo);
});
}));
Promise.all(promises).then(function(values) {
console.log(projectInfo) ;
});
This can also be simplified with the map function and async/await :
const promises = (response.project).map(async element => {
const respo = await dbo.collection("projects").findOne({_id:element});
projectInfo.push(respo);
});
Promise.all(promises).then(function(values) {
console.log(projectInfo) ;
});

findOneAndUpdate causing duplication problem

I am having a problem in findOneAndUpdate in mongoose.
The case is that i am updating a document by finding it.
The query is as follows:
UserModel.findOneAndUpdate({
individualId: 'some id'
}, {
$push: {
supporterOf: 'some string'
}
})
The 'supporterOf' is the ref of UserModel and its type is 'ObjectId'.
The issue i am facing here is that, 'some string' is being pushed twice under 'supporterOf' in the document.
Can anyone tell me that how to push an array element inside the document?
I was having same problem, solution is.
I was keeping await like below.
**await** schema.findOneAndUpdate(queryParms, {
"$push": {
"array1": arrayDetails,
"array2": array2Details
}
}, {
"upsert": true,
"new": true
},
function (error, updateResponse) {
if (error) {
throw new Error (error);
} else {
// do something with updateResponse;
}
});
simply removing await helped me resolving this problem.
Need to find the root cause.
any pointer for references are welcome.
I have recently encountered the same problem. However, I managed to overcome this issue by some other logics (details given below) but couldn't understand the reason behind that why findOneAndUpdate inserting duplicate entries in mongodb.
You can overcome this problem by following logic.
Use findOne or findById instead of findOneAndUpdate to search the document in your collection and then manually update your document and run save().
You can have better idea with this code snippet
return new Promise(function (resolve, reject) {
Model.findOne({
someCondition...
}, function (err, item) {
if (err) {
reject(err);
} else {
item.someArray.push({
someKeyValue...
});
item.save().then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
});
}
}).catch((err) => {
reject(err)
});
});
This will not insert duplicate item. However, if you come to know the reasoning behind duplication, must do update this thread.
The issue seems to stem from combining an await and a callback. I had the same issue until I realised I was using an (err, resp) callback and a .catch(...).
models[auxType].findOneAndUpdate(
filter,
updateObject,
options,
(err, resp)=>{
if (err) {
console.log("Update failed:",err)
res.json(err)
} else if (resp) {
console.log("Update succeeded:",resp)
res.json(resp)
} else {
console.log("No error or response returned by server")
}
})
.catch((e)=>{console.log("Error saving Aux Edit:",e)}); // << THE PROBLEM WAS HERE!!
The problem resolved as soon as I removed the .catch(...) line.
From the mongoose documentation:
"Mongoose queries are not promises. They have a .then() function for co and async/await as a convenience. However, unlike promises, calling a query's .then() can execute the query multiple times."
(https://mongoosejs.com/docs/queries.html#queries-are-not-promises)
Use $addToSet instead of $push, it should solve the problem. I believe there is an issue with the data structure used in the creation of a mongoose 'Model'. As we know push is an array (which allows duplication) operation while addToSet may be a Set operation (Sets do not allow duplication).
The problem with the accepted answer is that it only solves the problem by wrapping it in an unnecessary additional promise, when the findOneAndUpdate() method already returns a promise. Additionally, it uses both promises AND callbacks, which is something you should almost never do.
Instead, I would take the following approach:
I generally like to keep my update query logic separate from other concerns for both readability and re-usability. so I would make a wrapper function kind of like:
const update = (id, updateObj) => {
const options = {
new: true,
upsert: true
}
return model.findOneAndUpdate({_id: id}, {...updateObj}, options).exec()
}
This function could then be reused throughout my application, saving me from having to rewrite repetitive options setup or exec calls.
Then I would have some other function that is responsible for calling my query, passing values to it, and handling what comes back from it.
Something kind of like:
const makePush = async () => {
try {
const result = await update('someObjectId', {$push: {someField: value}});
// do whatever you want to do with the updated document
catch (e) {
handleError(e)
}
}
No need to create unnecessary promises, no callback hell, no duplicate requests, and stronger adherence to single responsibility principles.
I was having the same problem. My code was:
const doc = await model.findOneAndUpdate(
{filter}, {update},
{new: true}, (err, item) => if(err) console.log(err) }
)
res.locals.doc = doc
next();
The thing is, for some reason this callback after the "new" option was creating a double entry. I removed the callback and it worked.
I had the same problem.
I found a solution for me:
I used a callback and a promise (so using keyword "await") simultaneously.
Using a callback and a promise simultaneously will result in the query being executed twice. You should be using one or the other, but not both.
options = {
upsert: true // creates the object if it doesn't exist. defaults to false.
};
await Company.findByIdAndUpdate(company._id,
{ $push: { employees: savedEmployees } },
options,
(err) => {
if (err) {
debug(err);
}
}
).exec();
to
options = {
upsert: true // creates the object if it doesn't exist. defaults to false.
};
await Company.findByIdAndUpdate(company._id,
{ $push: { employees: savedEmployees } },
options
).exec();
UserModel.findOneAndUpdate(
{ _id: id },
{ object }
)
Even if you use _id as a parameter don't forget to make the filter explicit by id
In my case, changing the async callback solved the problem.
changing this:
await schema.findOneAndUpdate(
{ queryData },
{ updateData },
{ upsert: true },
(err) => {
if (err) console.log(err);
else await asyncFunction();
}
);
To this:
await schema.findOneAndUpdate(
{ queryData },
{ updateData },
{ upsert: true },
(err) => {
if (err) console.log(err);
}
);
if (success) await asyncFunction();
The $addToSet instead of $push allowed me to prevent duplicate entry in my mongoDb array field of User document like this.
const blockUserServiceFunc = async(req, res) => {
let filter = {
_id : req.body.userId
}
let update = { $addToSet: { blockedUserIds: req.body.blockUserId } };
await User.findOneAndUpdate(filter, update, (err, user) => {
if (err) {
res.json({
status: 501,
success: false,
message: messages.FAILURE.SWW
});
} else {
res.json({
status: 200,
success: true,
message: messages.SUCCESS.USER.BLOCKED,
data: {
'id': user._id,
'firstName': user.firstName,
'lastName': user.lastName,
'email': user.email,
'isActive': user.isActive,
'isDeleted': user.isDeleted,
'deletedAt': user.deletedAt,
'mobileNo': user.mobileNo,
'userName': user.userName,
'dob': user.dob,
'role': user.role,
'reasonForDeleting': user.reasonForDeleting,
'blockedUserIds': user.blockedUserIds,
'accountType': user.accountType
}
});
}
}
).catch(err => {
res.json({
status: 500,
success: false,
message: err
});
});
}

Construct array in nodejs through for loop

I am using sails (0.11.0) running on nodejs (6.9.1). I am trying to construct an array by filling it through for loop. I would send this completed array in response to the client. I have tried various methods as suggested by people here on Stack Overflow, for example
the discussion here suggested
for (var i = yearStart; i < yearEnd+1; i++) {
arr.push(i);
}
On this discussion, it is suggested to use:
var array = calendars.map(function(item) {
return item.id;
});
console.log(array);
Similarly I tried many methods but I am coming across the same issue that during the loop, the array gets filled but as soon as the loop is completed, the array gets empty because of asynchronous process and therefore I can not send the response. To tackle with this I tried checking the index inside the loop body and send response from inside the loop body itself through
var userArray = [];
_.each(users, function(user, index){
MySQLConnector.query('CALL user_image (?)', [user.id], function(err, userImage){
if(err){
return res.json({"status":"some_error"});
}else{
userID = user.id
userImageID = userImage[0][0].id;
var userInfo = {
userID: userID,
userImageID: userImageID
}
userArray.push(userInfo)
if(index == users.length - 1){
res.json({selectedUsers: userArray});
}
}
});
});
I am initiating an empty userArray and then iterate through users object where each element of the object is characterized by name user and an index. Through a MySQL query I am fetching the userImage object and in each iteration, I am creating an object called userInfo that consists of userID and userImageID. I am pushing this object into userArray. And after each iteratio of the for loop (_.each), I check if last index is reached. Once last index is reached, the final array is sent as response before loop body is complete.
Here too I have an issue that the array body is not always completely filled. The reason is due to asynchronous process, the index does not always follow the order 0,1,2,3,4,.... and it can start with any number and can jump to any index in the next iteration, for example the first index to start would be 4, the second would be 0, third would be 2 and so on. This sequence would be different for every time we run this for loop. For a user, it will appear to be a total random process. Therefore if users.length is 8, and current index is randomly 7 at third iteration, the condition index == users.length - 1 will be met and response will be sent just with an array consisting of 3 elements rather than 8.
Can someone suggest me a better and robust way to fill an array through the for loop in nodejs and send that array in response, so that all items are included in the array in their original order?
As you are using node js , it is better to use any promises library like bluebird or async to handle Async requests.
The reason your loop is not working as expected is because as you've pointed out, due to async requests taking time to resolve for which _.each loop is not waiting.
Using bluebird, it can be done with Promise.map method which works as explained below from the documentaion :
Given an Iterable(arrays are Iterable), or a promise of an Iterable,
which produces promises (or a mix of promises and values), iterate
over all the values in the Iterable into an array and map the array to
another using the given mapper function.
Promises returned by the mapper function are awaited for and the
returned promise doesn't fulfill until all mapped promises have
fulfilled as well. If any promise in the array is rejected, or any
promise returned by the mapper function is rejected, the returned
promise is rejected as well.
Hence, Using Promise.map your code can be updated like below :
var Promise = require("bluebird");
return Promise.map(users, function(user, index){
return MySQLConnector.query('CALL user_image (?)', [user.id], function(err, userImage){
if(err){
return Promise.reject({"status":"some_error"});
}else{
userID = user.id
userImageID = userImage[0][0].id;
var userInfo = {
userID: userID,
userImageID: userImageID
}
return userInfo;
}
});
})
.then(function (usersArray){
res.json({selectedUsers: usersArray});
})
.catch(function (err){
res.json(err);
});
You can execute loops with functions with callbacks synchronously using SynJS:
var SynJS = require('synjs');
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'tracker',
password : 'tracker123',
database : 'tracker'
});
function myFunction1(modules,connection,users) {
var ret=[];
for(var i=0; i<users.length; i++) {
connection.query("SELECT CONCAT('some image of user #',?) AS userImage", [users[i]], function(err, rows, fields) {
if (err) throw err;
ret.push({
id: users[i],
image: rows[0].userImage
});
modules.SynJS.resume(_synjsContext); // <-- indicate that callback is finished
});
SynJS.wait(); // <-- wait for callback to finish
}
return ret;
};
var modules = {
SynJS: SynJS,
mysql: mysql,
};
var users = [1,5,7,9,20,21];
SynJS.run(myFunction1,null,modules,connection,users,function (ret) {
console.log('done. result is:');
console.log(ret);
});
Result would be following:
done. result is:
[ { id: 1, image: 'some image of user #1' },
{ id: 5, image: 'some image of user #5' },
{ id: 7, image: 'some image of user #7' },
{ id: 9, image: 'some image of user #9' },
{ id: 20, image: 'some image of user #20' },
{ id: 21, image: 'some image of user #21' } ]

chained promise in for loop doesn't execute properly

I have chainable promises which are working fine in a single series but now i want to call this serios of chain inside for loop but it does not work as expected.
see my demo plunker and see the output in console.
below is the structure of my chaining promises . I want all publicIP which is returned by funTwo; but I want to complete funThree() and then want to get all publicIP. As I know that $q.when() makes a value in promise object.
but you can see that console.log('pA', promiseArray); executed very before and console.log('res three'); and why successHandler and finally called before that?
Here surely I am missing something , may be have to write a return; in proper place , kindly help me how to executed all function in for loop and return a data array after that for loop ends which can be retried in successHandler
MyService.funZero()
.then(function(response) {
console.log(response);
var promiseArray = [];
for(var i = 0; i < 2 ; i++) {
console.log('I', i);
MyService.funOne()
.then(MyService.funTwo)
.then(function(res2) {
console.log('res two', res2);
publicIP = res2.ip;
console.log('i', publicIP);
promiseArray.push({'ip': publicIP});
return MyService.funThree(publicIP);
})
.then(function() {
console.log('res three');
})
} // for loop ends
console.log('pA', promiseArray);
return $q.when(promiseArray);
})
.then(function(res4){
console.log('after for loop', res4);
})
.then(successHandler)
.catch(errorHandler)
.finally(final, notify);
So, I'm not sure exactly what MyService.funThree does, but you can aggregate an array via ipArray.push({'ip': publicIP}) and return that to the MyService.funThree and then the subsequent function. The issue here is there is no guarantee of order in the ipArray if that's what you're looking for. Here's the middle section of that function:
ipArray = [];
for(var i = 0; i < 2 ; i++) {
console.log('I', i);
var promise = MyService.funOne()
.then(MyService.funTwo)
.then(function(res2) {
console.log('res two', res2);
publicIP = res2.ip;
console.log('i', publicIP);
ipArray.push({'ip': publicIP});
return ipArray;
})
.then(MyService.funThree)
.then(function() {
console.log('res three');
});
promiseArray.push(promise);
}
console.log('pA', promiseArray);
return $q.all(promiseArray);

Resources