JS For loop is just not iterating, Why? - javascript

So, I am making this discord bot and I wanted to make it so if someone's message gets deleted, It can be stored onto a .txt file, And if someone wanted, They could just type in a command and the bot will show the list of deleted messages.
So, when the command is fired, it will need a number parameter and that parameter will be the number of messages that will be shown.
Let's call that number numberOfSnipes
The code will get the .txt file and turn it into an array by splitting each line and putting it in one array.
As so,
fs.readFile('deletedMsgs/' + message.guildId + '.txt', function (err, data) {
messagesArray = data.toString().split('\n');
});
As the code says, It will be called messagesArray
Now the problem comes here, We will need to iterate through the array and get the number of messages needed, This will be put into another array which will be called messagesToShow
So, I tried coding that and failed,
Code:
messagesToShow = []
for (let i = messagesArray.length; i < messagesArray.length - numberOfSnipes; i--) {
console.log(i)
messagesToShow.push(messagesArray[i])
}
FYI, console.log(i) did not log anything.
I tried to log messagesArray, It logged an empty array
Keep in mind that numberOfSnipes and messagesArray were both logged and they returned the right information.
Since, This can be more easier for you all. To solve, I will provide the whole code:
if (message.content.toLowerCase().startsWith(config.prefix + 'snipelist')) {
numberOfSnipes = parseInt(message.content.split(' ')[1]);
if (fs.existsSync('deletedMsgs/' + message.guildId + '.txt')) {
fs.readFile(
'deletedMsgs/' + message.guildId + '.txt',
function (err, data) {
messagesArray = data.toString().split('\n');
console.log(messagesArray);
if (numberOfSnipes > messagesArray.length) {
message.reply('Unable to return messages.');
} else {
messagesToShow = [];
for (
let i = messagesArray.length;
i < messagesArray.length - numberOfSnipes;
i--
) {
console.log(i);
messagesToShow.push(messagesArray[i]);
}
console.log(messagesToShow);
finalMessage = ' ';
for (let letter in messagesToShow.toString()) {
if (letter != ',') {
finalMessage += letter;
} else if (letter == ',') {
finalMessage += '\n';
}
}
message.reply(finalMessage);
}
}
);
}
}
Any help is appreciated.

Related

Undefined errors using Node.js, Mongoose, and Discord.js [Cannot read property of undefined]

I've been scouring similar problems but haven't seem to have found a solution that quite works on my end. So I'm working on a Discord bot that takes data from a MongoDB database and displays said data in the form of a discord embedded message using Mongoose. For the most part, everything is working fine, however one little section of my code is giving me trouble.
So I need to import an array of both all available users and the "time" data of each of those users. Here is the block of code I use to import said data:
for (i = 0;i < totalObj; i++){
timeArray[i] = await getData('time', i);
userArray[i] = await getData('user', i);
}
Now this for loop references a function I made called getData which obtains the data from MongoDB by this method:
async function getData(field, value){
var data;
await stats.find({}, function(err, result){
if(err){
result.send(err);
}else{
data = result[value];
}
});
if(field == "user"){
return data.user;
}else if (field == "time"){
return data.time;
}else{
return 0;
}
So that for loop is where my errors currently lie. When I try to run this code and display my data through a discord message, I get this error and the message does not get sent:
(node:13936) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'time' of undefined
Now the strange thing is, this error does not happen every time. If I continue calling the command that triggers this code from my discord server, it's almost like a 50/50 shot if the command actually shows the message or instead gives this error. It is very inconsistent.
This error is confounding me, as the undefined part does not make sense to me. The objects that are being searched for in the mongoDB collection are definitely defined, and the for loop never exceeds the number of objects present. My only conclusion is that I'm doing something wrong with my asynchronous function design. I have tried altering code to use the getData function less often, or to not use awaits or asynchronous design at all, however this leaves my final discord message with several undefined variables and an eventual crash.
If anyone has any advice or suggestions, that would be very much appreciated. Just for reference, here is the full function that receives the data, sorts it, and prepares a string to be displayed on the discord server (though the error only seems to occur in the first for loop):
async function buildString(){
var string = "";
var totalObj;
var timeArray = [];
var userArray = [];
var stopSort = false;
await stats.find({}, function(err, result){
if(err){
result.send(err);
}else{
totalObj = result.length;
}
});
for (i = 0;i < totalObj; i++){
timeArray[i] = await getData('time', i);
userArray[i] = await getData('user', i);
}
while(!stopSort){
var keepSorting = false;
for(i = 0; i < totalObj ; i++){
var target = await convertTime(timeArray[i]);
for(j = i + 1 ; j < totalObj ; j++){
var comparison = await convertTime(timeArray[j]);
if(target > comparison){
//Switch target time with comparison time so that the lower time is up front
var temp = timeArray[i];
timeArray[i] = timeArray[j];
timeArray[j] = temp;
//Then switch the users around so that the user always corresponds with their time
var userTemp = userArray[i];
userArray[i] = userArray[j];
userArray[j] = userTemp;
//The loop will continue if even a single switch is made
keepSorting = true;
}
}
}
if(!keepSorting){
stopSort = true;
}
}
//String building starts here
var placeArray = [':first_place: **1st', ':second_place: **2nd', ':third_place: **3rd', '**4th', '**5th', '**6th', '**7th', '**8th', '**9th', '**10th'];
for(i = 0; i < totalObj; i++){
string = await string.concat(placeArray[i] + ": " + userArray[i] + "** - " + timeArray[i] + " \n\n");
console.log('butt');
}
console.log("This String:" + string);
return string;
}
I think problem is you are trying to await function with callback, it will not work => access to data.time may run before data = result[value]. If you need await callback, you can use custom Promise (or use util.promisify, more info here)
Promise:
function findStats(options) {
return new Promise((resolve, reject) => {
return stats.find(options, function (err, result) {
if (err) {
return reject(err)
}
return resolve(result)
})
})
}
utils.promisify
const util = require('util');
const findStats = util.promisify(stats.find);
Now you can use await in your function
async function getData(field, value) {
try {
const result = await findStats({})
const data = result.value
if (field === 'user') {
return data.user
}
if (field === 'time') {
return data.time
}
return 0
} catch (error) {
// here process error the way you like
// or remove try-catch block and sanitize error in your wrap function
}
}

Javascript readdirSync - count number of strings outside function

I'm trying to write a script that allows me to search for a string in a number of folders and then return the output. I've managed to find the readdirSync function which does what I want, however I am unable to print out the total number of strings found because it is ran before the function is complete. Please see example below.
var totalNumberOfStringFound = 0;
function numberofStringsInFolder(dir, search) {
var fs = require('fs');
var results = [];
var searchTerm = search;
fs.readdirSync(dir).forEach(function (file) {
file = dir + '/' + file;
var stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results = results.concat(numberOfTagsInFolder(file));
} else {
fs.readFile(file, bar);
function bar(err, data) {
err ? Function('error', 'throw error')(err) : (fileContent = data.toString('utf8'));
var count = (fileContent.match(new RegExp(searchTerm, 'gi')) || []).length;
totalNumberOfStringFound = totalNumberOfStringFound + count;
console.log('NUMBER OF STRINGS FOUND: ' + totalNumberOfStringFound);
}
// Holds the list of files found
results.push(file);
}
});
console.log("totalNumberOfStringFound: " + totalNumberOfStringFound);
return results;
}
numberofStringsInFolder('./folder', 'HELLO');
Output
totalNumberOfStringFound: 0
NUMBER OF STRINGS FOUND: 2
NUMBER OF STRINGS FOUND: 3
The output (totalNumberOfStringFound) should be 3, but because totalNumberOfStringFound is called before the function has finished, it is showing as 0. I've looked online and some people use the timeout function, but I don't want to use that because I don't know how long it will take to complete. I would really appreciate it if someone can help. Thank you

How can I run recursive function in cypress or find length with async await

I am running tests using Cypress.
I have an array of Litecoin addresses. I am trying to set first in the input. Then submit the form.
If the address is duplicate then a notification is displayed and submit button will be not visible. The same I want to set for the second element and so on till end of the array.
I tried recursive function:
function runTillElementFound (totalCount, currentCount, litecoin_addresses)
{
var self = this;
if (currentCount < totalCount) {
return cy.get('body').then(($body) =>
{
if ($body.find(dash_page.save_wallet_circle_btn)) {
//if there is save button then set address and submit form
cy.log('taken address: ' + litecoin_addresses[ currentCount ]);
dashact.fill_wallet(litecoin_addresses[ currentCount ]);
cy.log('address is filled');
dashact.submit_wallet(true, 0);
self.runTillElementFound(totalCount, currentCount++);
}
});
} else {
return false; //if element not present after Max count reached.
}
I try to call it:
it('Set wallet', () =>
{
cy.log('this is array length: ' + litecoin_addresses);
runTillElementFound(20, 0, litecoin_addresses);
/* comact.submit_form(true, 1);
let ltc_address = promisify(dashact.get_wallet_value());
cy.log('this is address: ' + ltc_address);
//close popup and check that it is closed:
popact.submit_payment(); */
});
However I receive undefined:
I have also tried non recursive function:
for (var i = 0; i < litecoin_addresses.length; i++) {
cy.log('taken address: ' + litecoin_addresses[ i ])
if (litecoin_addresses[ i ] == wallet_before_edit || litecoin_addresses[ i ].length == 0 || litecoin_addresses[ i ].startsWith('ltc')) {
continue;
}
else {
cy.log('this is curent i: ' + i);
dashact.fill_wallet(litecoin_addresses[ i ]);
dashact.submit_wallet(true, null);
cy.get('body').then(($body) =>
{
// synchronously query from body
// to find which element was created
if ($body.find(com_page.not_message).length) {
// error was found, do something else here
cy.log('error was found');
}
else {
cy.log('error not found');
// input was not found, do something else here
i = litecoin_addresses.length;
cy.log('current i value: ' + i);
}
})
}
However it for sure, does not work, as i inside promise has one valued but in the loop it still remains the same.
If you use a specific array in your test code, you can easily get the length of the array by using the .lenght and access its elements by using the for loop.

How replace the text in a huge number of content controls via word-js?

I am trying to write a function which takes a list of Rich Text Content Controls and a single string as argument, and which replaces the content of all matching content controls with this string.
While this works with a smaller amount of content controls, it fails with documents with a huge amount of them. I have to work with documents with over 700 Content Controls with individual titles. In this case, the code just replaces the first 66X CCs and then aborts with a GeneralException. I assume this is just due to the huge amount of content controls. I am having similar problems, when I try to register bindings for all these CCs (GeneralException). But this is a different topic.
I tried to work around this problem, by limiting the amounts of changes per .sync() and looping through the CCs, performing as many loops as necessary. However, this is not that easy, due to the asynchronous nature of office-js. I am not very familiar with javascript-async-promise-programming so far. But this is what I have come up with:
function replaceCCtextWithSingleString (CCtitleList, string) {
var maxPerBatch = 100;
/*
* A first .then() block is executed to get proxy objects for all selected CCs
*
* Then we would replace all the text-contents in one single .then() block. BUT:
* Word throws a GeneralException if you try to replace the text in more then 6XX CCs in one .then() block.
* In consequence we only process maxPerBatch CCs per .then() block
*/
Word.run(function (context) {
var CCcList = [];
// load CCs
for(var i = 0; i < CCtitleList.length; i++) {
CCcList.push(context.document.contentControls.getByTitle(CCtitleList[i]).load('id'));
}
return context.sync().then(function () { // synchronous
var CClist = [];
// aggregate list of CCs
for(var i = 0; i < CCcList.length; i++) {
if(CCcList[i].items.length == 0) {
throw 'Could not find CC with title "'+CCtitleList[j]+'"';
}
else {
CClist = CClist.concat(CCcList[i].items);
}
}
$('#status').html('Found '+CClist.length+' CCs matching the criteria. Started replacing...');
console.log('Found '+CClist.length+' CCs matching the criteria. Started replacing...');
// start replacing
return context.sync().then((function loop (replaceCounter, CClist) {
// asynchronous recoursive loop
for(var i = 0; replaceCounter < CClist.length && i < maxPerBatch; i++) { // loop in loop (i does only appear in condition)
// do this maxPerBatch times and then .sync() as long as there are still unreplaced CCs
CClist[replaceCounter].insertText(string, 'Replace');
replaceCounter++;
}
if(replaceCounter < CClist.length) return context.sync() // continue loop
.then(function () {
$('#status').html('...replaced the content of '+replaceCounter+' CCs...');
return loop(replaceCounter, numCCs);
});
else return context.sync() // end loop
.then(function () {
$('#status').html('Replaced the content of all CCs');
});
})(0, CClist));
});
}).catch(function (error) {
$('#status').html('<pre>Error: ' + JSON.stringify(error, null, 4) + '</pre>');
console.log('Error: ' + JSON.stringify(error, null, 4));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo, null, 4));
}
throw error;
});
}
However... it is not working. It replaces the first 100 CCs and then stops. Without a failure, without an exception or anything. The return loop(replaceCounter, CClist); is just not executed and I don't know why. If I try to step into this line in the debugger it throws me somewhere in the office-js code.
Any suggestions?
Edit:
I updated my code based on the suggestions of Juan Balmori and it works as a charm:
function replaceCCtextWithSingleString_v1_1 (CCtitleList, string) {
Word.run(function (context) {
var time1 = Date.now();
// load the title of all content controls
var CCc = context.document.contentControls.load('title');
return context.sync().then(function () { // synchronous
// extract CC titles
var documentCCtitleList = [];
for(var i = 0; i < CCc.items.length; i++) { documentCCtitleList.push(CCc.items[i].title); }
// check for missing titles and replace
for(var i = 0; i < CCtitleList.length; i++) {
var index = documentCCtitleList.indexOf(CCtitleList[i]);
if(index == -1) { // title is missing
throw 'Could not find CC with title "'+CCtitleList[i]+'"';
}
else { // replace
CCc.items[index].insertText(string, 'Replace');
}
}
$('#status').html('...replacing...');
return context.sync().then(function () {
var time2 = Date.now();
var tdiff = time2-time1;
$('#status').html('Successfully replaced all selected CCs in '+tdiff+' ms');
});
});
}).catch(function (error) {
$('#status').html('<pre>Error: ' + JSON.stringify(error, null, 4) + '</pre>');
console.log('Error: ' + JSON.stringify(error, null, 4));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo, null, 4));
}
});
}
It still takes 13995 ms to complete, but at least it works :-)
Any ideas, what was provoking the GeneralException though?
I posted a new question concerning the speed issue: What is the fastest way of replacing the text of many content controls via office-js?
Good Question.. I did some perf test long time ago and I was able to change more than 10k content controls in a document. with 700 you should be ok.
Not sure why are you pre-filling a list, that is not needed, you are actually navigating 2 times the collection which is not good for perf. You can do the string comparison while traversing the collection!
Here is an example, I just did a quick test with a 700 content control document with a hypothetical tag of "test".
I was able to
1. Compare their text against whatever you want to compare it (its a string)
2. Change the value if the condition is true.
It took 5134 milliseconds to complete the operation and here is the code. which I think its quite acceptable.
Hope this helps!
function perfContentControls() {
var time1 = Date.now(); // lets see in how much time we complete the operation :)
var CCs =0
Word.run(function (context) {
var myCCs = context.document.body.contentControls.getByTag("test");
context.load(myCCs);
return context.sync()
.then(function () {
CCs = myCCs.items.length
for (var i = 0; i < CCs; i++) {
if (myCCs.items[i].text == "new text 3") // you can check the cc content and if needed replace it....
myCCs.items[i].insertText("new text 4", "replace");
}
return context.sync()
.then(function () {
var time2 = Date.now();
var diff = time2 - time1;
console.log("# of CCs:" + CCs + " time to change:" + diff + "ms");
})
})
.catch(function (er) {
console.log(er.message);
})
})
}

Code isn't executing the full script

I wrote some code that checks a list, and checks if each item in the list is present in the other one. If the item isn't found, it adds it to the database.
The scanning code is correct (the part that says db.scan) but somewhere towards the end the code isn't going through because its not executing the console.log part (Where it says "Entering journal into database..." title of article"
When I execute this code, nothing happens. At least there are no errors... but its not even logging the console.log parts so something is wrong.
// accessing the database
function DatabaseTime(sourcesDates, timeAdded, links, titles, descriptions) {
sourcesDates = sourcesDates;
links = links;
titles = titles; // this will be used to check on our articles
descriptions = descriptions;
var autoParams;
var databaseOperation = function (sourcesDates, timeAdded, links, titles, descriptions) {
var scanParams = { TableName: "Rnews" }
// using code to setup for accessing the 2nd list
db.scan(scanParams, function(err, scanData) { // scanData = the 2nd list we are going to work with
var counter = 0; // just a way to help make my code more accurate as seen later in the loops
var counter2 = 0;
// this is the first list iterating on
for (var i = 0; i < links.length; i++) {
counter = 0;
// looping through items in second list
for (var x = 0; x < scanData.Items.length; x++) {
// if article is not in db
if (titles[i] !== scanData.Items[x].title) {
continue;
}
else if (titles[i] === scanData.Items[x].title) {
// intention is to immediately move to the next item in the first list if this block executes
console.log("Article found: \"" + titles[i] + "\". Not proceeding anymore with article.");
counter++;
break;
} else {
// if this article isnt found anywhere in the list we are checking on, add to database
if (x === scanData.Items.length && counter !== 0) {
autoParams = {
TableName: "Rnews",
Item: {
title: titles[i],
source: sourcesDates[i],
url: links[i],
description: descriptions[i],
lastAddOrUpdated: dbTimeStamp,
timePublish: timeAdded[i]
}
}
console.log("Entering journal to database: " + titles[i]);
db.put(autoParams, function(err, data) {
if(err) throw err;
});
//}
}
}
}
}
});
//console.log("Complete");
};
databaseOperation(sourcesDates, timeAdded, links, titles, descriptions);
}
//// END
You never called the function DatabaseTime. Your code just declares the function and does nothing else. In order for the function to execute, you must invoke it.

Categories